1

Hundred times a day I need to search for patterns in files and sometime I have to replace these patterns with something else. Most of the time it is simple patterns like a word or a short sentence but sometime I have to look for more complex regexp. I don't really like sed (at least the sed version I have because it is not much compliant with the PCRE engine). So I rather prefer using perl -pi -e.

However, Perl pie is not very attractive on Cygwin because of the mandatory -i.bak temp files. I need to find a way to automatically remove the .bak files after processing. Moreover, if I want to replace recursively in a project I have to list all the files first:

 find . | xargs -n1 perl -pi -e 's/foo/bar/' 

This command is quite long to write especially if you use it thousand times a month. So I decided to write a more useful tool working in the same way as the great silver searcher ag.

 ag 'foo\d{3}[^\w]' # Search for a pattern # Oh yes this one should be renamed! replace 's/(foo)\d{3}[^\w]/\U$1\E_bar/g' 

I wrote this very primitive bash function

function replace { EXTENSION=.perlpie_tmp perl -p -i$EXTENSION -e $1 ${*:2} for file in ${*:2}; do rm "$file$EXTENSION"; done; } 

But I am not satisfied at all because it doesn't automatically search for all files recursively if there is no more than one argument. I may either modify this function an add find . if the number of arguments is 1, or I can write a much complex program in Perl that can support command line options, pretty output, smart case search or even plain text search.

What is the most suitable option to this problem and is there any advanced search/replace tool on the linux world? If not I may try to write my own rip tool standing for replace-in-place which can support all the options that I need.

Before that I need some advices...

EDIT

Actually I think to fork https://github.com/petdance/ack2 to add a replacement feature... This may or may not be a good idea...

3 Answers 3

3

Here's an alternative to your function (edited to use the suggestion provided by gniourf_gniourf, thanks):

find -type f . -exec sh -c 'perl -pi.bak -e "s/foo/bar/" "$0" && rm -f "$0".bak' {} \; 

Using this approach, you can remove the file as you go.

Sign up to request clarification or add additional context in comments.

5 Comments

{} is not supposed to be used that way! Think of a file named dummy; find / -delete;... ooops. Besides, the call to sh is really useless.
I never think to use -exec because for an obscure reason it is quite slow on Cygwin. However it's a good alternative to my function :)
@gniourf_gniourf it seems I've completely misunderstood what {} is supposed to be used for then! Would the alternative to sh be to use two -exec?
When using {} with sh or bash, use it as an argument: -exec sh -c 'perl -pi.bak -e "s/foo/bar/" "$0" && rm -f "$0".bak' {} \;. Yes, the positional argument here is 0–if you like 1 better, do: -exec sh -c 'perl -pi.bak -e "s/foo/bar/" "$1" && rm -f "$1".bak' _ {} \;. Otherwise, used as you did, is both dangerous and broken for filenames containing spaces. Alternative to sh (in this case) is to use two -execs, yes (the second one being executed only if the first one succeeds, so it's really equivalent to your &&).
While it most def requires a change of logic here, the + argument to find might be worth looking into if Cygwin -exec is slow. {} \; passes the exec one argument at a time, while {} + will pass multiple arguments (find results) at a time. The number of times you call your exec statement will be drastically reduced.
0

I think you can use

grep -Hrn -e "string" . 

to find a pattern, and

find -type f -exec sed -i "s@string1@string2@g" {} \; 

to replace a pattern

2 Comments

but I would like to avoid sed because it's implementation differs from a distribution on another. I feel the PCRE engine is way much better than the horrible [[:digit:]] in sed
Maybe you can use awk. For example : awk -Fstring1 '{printf $1;for(i=2;i<=NF;i++) printf "%s%s","string2",$i; printf "\n"}' filename > dummyfile; mv dummyfile filename
0

I would slightly modify your existing function:

function replace { local perl_code=$1 EXTENSION=.perlpie_tmp file shift for file; do perl -p -i$EXTENSION -e "$perl_code" "$file" && rm "$file$EXTENSION" done; } 

This will slightly worsen the performance as you're now calling perl multiple times, but I suspect you won't notice.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.