Re: Re: Re: Need one-liner to s///g in all sub-dirs
by rob_au (Abbot) on Aug 25, 2001 at 08:14 UTC
|
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! | [reply] [d/l] [select] |
|
|
I have indeed read perlman:perlrun (several times this
evening) and I recognize the code you are quoting from that
source. I am sure all this seems pretty trivial to folk
who use it all the time -- but it is daunting to the
uninitiated. And frustrating. I could have writen several
scripts to do this task using different and interesting
approaches and
taken a looong walk in the time I have spent
on this trying to get some experience with command line
switches. I am presuming upon your patience...
Okay: Encourged by your insistence,
I finally found this bit in Camel 3, page 496:
The -p loop also contains an
implicit select(ARGVOUT); for each file.
perlrun just says,
"Note that the lines are printed automatically."
It omits to say at that point
"to the file in question". It's probably there somewhere.
And it's clear once you know it. I just missed it.
All of which brings my to my tired question (revised
a little each time). And I ask again because this part is
still not clear. Assuming that the files are already
backed up, does not this simple line:
%perl -pie 's/old/new/g' ./*/*
do everything I need without the use of find....
When I am doing things on unfamiliar turf, I like
(if possible) to keep it simple and clear.
Update: Still reading... perlrun does contain the
explanation of the implicit select(ARGVOUT);.
But, as in Camel 3 (which simply reprints the whole
section) it is under the -i, not -p. IMHO, the briefer
code given under -p is misleading since it claims to
explain what -p does but fails to mention the implicit
'select'. This is a crucial detail and the reason I
deliberately chose not to use it at first. I had no
need to print to STDOUT (which is what the code under
-p, as given, would do).
| [reply] [d/l] |
|
|
ls -d ./*/*
To list the files the 'find' solutions would affect, use:
find . -type f -print
-Blake | [reply] [d/l] [select] |
|
|
|
|
|
|
- All of which brings my to my tired question (revised a little each time). And I ask again because this part is still not clear. Assuming that the files are already backed up, does not this simple line:
%perl -pie 's/old/new/g' ./*/*
It comes close ... what this line does not do is act on the files of the sub-directories of your sub-directories. Wih your original method, files in the current directory and its immediate subdirectories are being acted upon, but no further. No files in subdirectories therein would be acted upon, negating the 'all sub-directories' requirement of your one-liner. Depending on your directory structure, this could leave a large number of files untouched.
The best way to describe this is through example. From the /usr directory of my mail server:
budapest:/usr# ls -l ./*/* | wc -l
4505
budapest:/usr# find . -type f -print | wc -l
22838
The difference in number of files is quite apparent - If however, you have no further subdirectories beneath those of your current directory, then there should be no problems, but if there is, potentially a large number of files could be missed.
Ooohhh, Rob no beer function well without!
| [reply] [d/l] [select] |
|
|
|
|
The documentation of -p omits to say that the lines are printed "to the file in question" because -p does not turn on that behavior. -p creates a loop which reads in a line into $_ with <>, executes your code, and prints $_. It prints $_ to the currently selected filehandle, which is STDOUT by default.
It's the -i switch that enables the "...to the file in question" behavior. The documentation explains that -i "specifies that files processed by the <> construct
are to be edited in-place. It does this by renaming
the input file, opening the output file by the
original name, and selecting that output file as the
default for print() statements."
Note that the -i command line switch actually takes an optional argument; an extension that is used to make a backup copy of the original file. For example, -i~ would create a backup file with a tilde at the end of the name, a la emacs.
Since -i takes an argument, perl -pie 's/old/new/g' ./*/* treats the letter e as the backup extension, and tries to open a script named 's/old/new/g'. The -i and -e switches need to be separated:
perl -pi -e 's/old/new/g' ./*/*
| [reply] [d/l] [select] |