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)