find + perl + xargs + mv
xargs -n2
makes it possible to print two arguments per line. When combined with Perl's print $_
(to print the $STDIN first), it makes for a powerful renaming tool.
find . -type f | perl -pe 'print $_; s/input/output/' | xargs -d "\n" -n2 mv
Results of perl -pe 'print $_; s/OldName/NewName/' | xargs -n2
end up being:
OldName1.ext NewName1.ext
OldName2.ext NewName2.ext
OldName3.ext NewName3.ext
OldName4.ext NewName4.ext
I did not have Perl's rename
readily available on my system.
How does it work?
find . -type f
outputs file paths (or file names...you control what gets processed by regex here!)
-p
prints file paths that were processed by regex, -e
executes inline script
print $_
prints the original file name first (independent of -p
)
-d "\n"
cuts the input by newline, instead of default space character
-n2
prints two elements per line
mv
gets the input of the previous line
My preferred approach, albeit more advanced.
Let's say I want to rename all ".txt" files to be ".md" files:
find . -type f -printf '%P\0' | perl -0 -l0 -pe 'print $_; s/(.*)\.txt/$1\.md/' | xargs -0 -n 2 mv
The magic here is that each process in the pipeline supports the null byte (0x00) that is used as a delimiter as opposed to spaces or newlines. The first aforementioned method uses newlines as separators. Note that I tried to easily support find .
without using subprocesses. Be careful here (you might want to check your output of find
before you run in through a regular expression match, or worse, a destructive command like mv
).
How it works (abridged to include only changes from above)
- In
find
: -printf '%P\0'
print only name of files without path followed by null byte. Adjust to your use case-whether matching filenames or entire paths.
- In
perl
and xargs
: -0
stdin delimiter is the null byte (rather than space)
- In
perl
: -l0
stdout delimiter is the null byte (in octal 000)