Search code examples
macosperlsedgrep

Issues with pattern searching with `grep` and then replacing in Mac OS


There are many similar posts available on SO. However, the answers given there do not work for me. I am using Mac OS Monterey with version 12.6.5.

I am searching all the files in a directory that contains a pattern or text-part using grep. Then I want to pipe that filename and use perl -pe or sed along with inline option -i. However, it does not work on my Mac! The minimal code is given below.

newprojectname=NEW
oldprojectname=OLD
dir=.
grep $oldprojectname $dir -lr --exclude test.sh 
grep $oldprojectname $dir -lr --exclude test.sh | xargs perl -i -pe's/oldprojectname/$newprojectname/g'  
# Also tried
grep $oldprojectname $dir -lr --exclude test.sh | xargs sed  -i.'' 's/oldprojectname/$newprojectname/g'  
echo `sw_vers`
cat t1.txt t2.txt

Output:

    ./.test.sh.swp
    ./t1.txt
    ./t2.txt
    sed: RE error: illegal byte sequence
    ProductName: macOS ProductVersion: 12.6.5 BuildVersion: 21G531
OLD

OLD

You can see the grep operation successfully found two files containing the searched pattern OLD. However, OLD has not been replaced by NEW. So that is the issue here. I am intentionally keeping the unintentionally generated .swp file so that the answer takes care of binary files as well.

Can this be sorted out with a simple modification to my script?


Solution

  • There are two problems with your script.

    First, you've forgotten the $ on $oldprojectname.

    ... | xargs perl -i -pe's/oldprojectname/$newprojectname/g'
                              ^
                            there
    

    Second, as pointed out in the comments, with single quotes shell variables won't be interpreted. Perl will literally get s/$oldprojectname/$newprojectname/g'. For something this simple, use ".

    ... | xargs perl -i -pe "s/$oldprojectname/$newprojectname/g"
    

    For something more complicated, perl and shell quoting can get mixed up, they both use $; you could stick with single quotes and export the shell variables as environment variables.

    export new=this
    expert old=that
    
    ... | xargs perl -i -pe 's/$ENV{old}/$ENV{new}/g'
    

    There's no need to first grep for files containing the old text, it's redundant. You can search and replace every file in one pass. Use find to feed Perl the files.

    find $dir -type f -not -name 'test.sh' -print0 | xargs -0 perl -i -pe 's/$old/$new/g'
    

    Files which do not contain $old will be unaffected (verify it doesn't change the modified time, tho).