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

Comfortable as I am with Perl CGI, DBI, OO, regexen, etc. ...*cough*, I treat command-line stuff as foreign turf.

Now I need to make a simple change in all files in all subdirectories of the current directory. So here is a learning opportunity. I have Camel at my elbow and the docs on the screen -- but the switch options are a bit dizzying.

Here's my attempt: %perl -ne 's/old/new/g' ./*/* This is mission-critical so I need to get it right. (I am backing up the whole mess.) Am I on the right track?

Replies are listed 'Best First'.
Re: Need one-liner to s///g in all sub-dirs
by rob_au (Abbot) on Aug 25, 2001 at 07:22 UTC
    Making use of other shell utilities, this is the most immediate solution which comes to mind for me:
     
    find . -type f -exec perl -pi.bak -e 's/old/new/g' {} \;

     
    If you want to do it 'all in Perl', find2perl makes it easier still:
     
    rob@budapest:~$ find2perl . -type f -exec perl -pi.bak -e 's/old/new/g +' {} \; #!/usr/bin/perl-5.005 eval 'exec /usr/bin/perl -S $0 ${1+"$@"}' if $running_under_some_shell; require "find.pl"; # Traverse desired filesystems &find('.'); exit; sub wanted { (($dev,$ino,$mode,$nlink,$uid,$gid) = lstat($_)) && -f _ && &exec(0, 'perl','-pi.bak','-e','s/old/new/g','{}'); } sub exec { local($ok, @cmd) = @_; foreach $word (@cmd) { $word =~ s#{}#$name#g; } if ($ok) { local($old) = select(STDOUT); $| = 1; print "@cmd"; select($old); return 0 unless <STDIN> =~ /^y/; } chdir $cwd; # sigh system @cmd; chdir $dir; return !$?; }

     
    If you then wanted to take out the forking of a separate perl process for the editing of each file, then I would recommend changing the &exec() call to something similar to that posted by tachyon recently at Inplace Editing.
     

     
    Ooohhh, Rob no beer function well without!
      dvergin struggles to follow the thread...
      Assuming I've already made the backups. What does find . -type f -exec perl -pi.bak -e 's/old/new/g' {} \; do that my suggestion does not do.

      And why '-p' instead of '-n'.

      Oops! Found another switch. I now submit: %perl -nie 's/old/new/g' ./*/*

        Okay, the find . -type f -exec perl -pi.bak -e 's/old/new/g' {} \; command finds all files under the current directory and passes them through the command perl -pi.bak -e 's/old/new/g' {} which the {} delimiter is replaced with the filename. This I feel is the easiest way to find all files within a given directory tree and pass them through the perl command for in-place modification. Yes it isn't a "pure-Perl" solution, but sometimes reinventing the wheel isn't the best approach to a problem. And that too, as always, TIMTOWTDI.
         
        The -n and -p command line switches are similar, both creating a loop similar to this around the passed -e code:
         
        # -n switch execution # LINE: while (<>) { .... # your command is executed here }

         
        The difference is that the -p causes an additional print statement to be incorporated into the loop which is important so that your modified code is printed back out to the file. For example:
         
        # -p switch execution # LINE: while (<>) { .... # your command is executed here } continue { print; } }

         
        Assuming you have already made your backups, you can remove the -i.bak switch which would made a backup of modified files, moving then to name.bak.
         

         
        Update : The Perl command line switches are well documented at perlman:perlrun with additional examples.
         

         
        Ooohhh, Rob no beer function well without!
Re: Need one-liner to s///g in all sub-dirs
by runrig (Abbot) on Aug 25, 2001 at 10:15 UTC
    Efficient and compact unix one liner (faster than using -exec on the find command) combining shell and perl, but dangerous if filenames contain newlines or other whitespace (I think some find's have an option to quote the output, but I haven't seen one yet):
    find . -type f | xargs perl -pi -e 's/old/new/g'
    See perldoc perlrun for the -p and -i option and modify to '-pi.bak' or somesuch if you want a backup of all files.