http://www.perlmonks.org?node_id=497502

pkk has asked for the wisdom of the Perl Monks concerning the following question:

perl -pi -w -e 's/find/replace/g;' *.txt

How do I make the above one-liner to find and replace in sub-directories ?

Thanks.

Replies are listed 'Best First'.
Re: One-line shell script for find and replace
by mrkoffee (Scribe) on Oct 05, 2005 at 07:47 UTC
    One way to do it, assuming you have find available:
    perl -pi -w -e 's/find/replace/g' `find ./ -name *.txt`
      1. No need for the / after the .
      2. You forgot to put a \ in front of *
        If you don't put it there your find will fail as soon as you have *.txt files in the current directory because the shell will replace "*.txt" by a list of those files.
      So:
      perl -pi -w -e 's/find/replace/g' `find . -name \*.txt`
      OTOH: I prefer to use xargs for situations like this, just because the command line usually has limits.
      find . -name \*.txt -print0 | xargs -0 perl -pi -w -e 's/find/replace/ +g'

      $\=~s;s*.*;q^|D9JYJ^^qq^\//\\\///^;ex;print

        I still prefer:

        find . -name '*.txt' -exec perl -pi -e 's/find/replace/g' {} \;
        daniel
      Oh, and in addition to Skeeve's comments, I don't really like the backtics, be it in Perl or in shell. So I generally stick with qx/.../ and $() respectively, although I'm not really sure whether the latter is portable. For sure it does work in bash, and has the added benefit that it's nestable.
      There's a chance here of the combined list of files overrunning the shell command line buffer.
      perl -MFile::Find -i -e 'find(sub{@ARGV=($_);s/find/replace/ while <>} +,".")'

        Close, but you can do better.

        perl -MFile::Find -pi -e'BEGIN { find( sub { push @ARGV, $File::Find:: +name if /\.txt\z/i }, "." ) } s/find/replace/'

        With File::Find::Rule you write it more nicely.

        perl -MFile::Find::Rule -pi -e'BEGIN { @ARGV = File::Find::Rule->name( + "*.txt" )->in( "." ) } s/find/replace/'

        But in practice I’d use find -print0 | xargs -0 for this. Way too much work to do it in Perl.

        Makeshifts last the longest.

Re: One-line shell script for find and replace
by EvanCarroll (Chaplain) on Oct 05, 2005 at 07:48 UTC
    the easiest way would be to do something like 'find ./ |  xargs perl -i -pwe 's/find/replace/g'
    Alternativly see File::Find


    Evan Carroll
    www.EvanCarroll.com
      But this will call perl over and over again which may not be desirable. Of course it may be the best way (up to doing it entirely in perl) to do it, if the files are too many. Update: thanks to EvanCarroll for pointing out my error.

      Incidentally I was a big user of xargs myself, until someone pointed out to me the -exec parameter, although I find its syntax to be somewhat awkward -- but I can live with that.

        This simply isn't true. Read the docs.
        find ./ | xargs perl -e'print "\n@ARGV"'
        Should output one line with all of the arguements sent to perl.

        Alternativly, just for the sake of putting this out there, this can be done without xargs
        find ./ -exec perl -e'print "\n@ARGV" {} \;
        But this would execute numerous copies of perl.

        But, fear not! for find has an altnative syntax!
        find ./ -exec perl -e'print "\n@ARGV"' {} +
        Which is also probably the best way to acomplish this task.


        Evan Carroll
        www.EvanCarroll.com
Re: One-line shell script for find and replace
by Moron (Curate) on Oct 05, 2005 at 10:51 UTC
    When addressing certain facts; that the OP did not specify what OS they are running (could be windoze, VMS or mac); that, even if it is unix or linux, files in hidden directories or called .anything.txt should probably be left alone (I believe files beginning with '~' for windows, although I don't do much windows) and also that for example find -print0 works on linux but not unix; a short script instead of a one liner would become necessary, perhaps this:
    # a unix/linux version: Traverse('./'); sub Traverse{ my $dir = shift; opendir my $dh, $dir or die "$!, for $dir"; for my $file ( grep !/^\./, readdir $dir ) { # but adjust for OS my $path = "$dir/$file"; if ( -d $path ) { Traverse( $path ); } else { ( $file =~ /\.txt$/ ) and Process( $path ); # or for VMS /\.TXT\;\d+$/ } } closedir $dh; } sub Process{ my $file = shift; open my $fh "<$file" or die "$!, for $file"; my @updated = (); while( <$fh> ) { s/find/replace/g; push @updated; } close $fh; open $fh, ">$file" or die "$!, for $file"; print $fh @updated; close $fh; }

    -M

    Free your mind

Re: One-line shell script for find and replace
by PodMaster (Abbot) on Oct 05, 2005 at 07:50 UTC
    perl -pi -w -e 's/find/replace/g;' subdirectory/*.txt
    ??

    MJD says "you can't just make shit up and expect the computer to know what you mean, retardo!"
    I run a Win32 PPM repository for perl 5.6.x and 5.8.x -- I take requests (README).
    ** The third rule of perl club is a statement of fact: pod is sexy.

      perl -pi -w -e 's/find/replace/g;' subdirectory/*.txt [download]
      ??
      To quote from the root node:
      perl -pi -w -e 's/find/replace/g;' *.txt
      How do I make the above one-liner to find and replace in sub-directories ?
      which suggests he wants to traverse a directory tree recursively.
        Eg:
        1. home/xyz.txt
        2. home/contacts/abc.txt
        3. home/contacts/addresses/pqr.txt

        Within the 'home' directory I need to find and replace a particular word in all text files (xyz.txt, abc.txt, pqr.txt)