Batch rename in Bash
There are a few tools that provide batch renaming for the shell, but most of them are quite huge and need installing. It is not necessary to use these utilities, because unix already all tools necessary:
$ ls -d *.txt | sed 'p;s/foo/bar/' | xargs -l2 mv
The first part should be quit clear: Print all files ending with
txt
.
The \*.txt
makes use of the shell's globbing features
and filters all the .txt
files.
The -d
switch tells ls
not to print the contents of directories.
The sed expression consists of two parts: p
prints
the current line and s/foo/bar/
is the actual transformation
(in this case: a replacement).
If I run this on my home directory I get this:
$ ls
down duh files fuu fuubar pr0j tmp usr
$ ls | sed 'p;s/fuu/bar/'
down
down
duh
duh
files
files
fuu
bar
fuubar
barbar
pr0j
pr0j
tmp
tmp
usr
usr
Notice that most files have just been printed twice,
but fuu
and fuubar
where changed to bar
and barbar
.
Now comes the tricky bit: xargs
takes each two lines
and applies them to mv
as arguments,
so when I run xargs in debug mode I get this:
$ ls | sed 'p;s/fuu/bar/' | xargs -l2 echo mv
mv down down
mv duh duh
mv files files
mv fuu bar
mv fuubar barbar
mv pr0j pr0j
mv tmp tmp
mv usr usr
Notice that I did not use the '-d'
flag this time,
because I print the content of the directory '.'
this time,
not a list of given files.
This would happen if I did use '-d'
.
$ ls -d
.
$ ls | sed 'p;s/fuu/bar/' | xargs -l2 echo mv
mv . .
Getting complicated
In the above example I did not do any filtering,
because the files foo
and foobar
(which existed before I began to write this article)
do not have an extension, but I could use a filter to select only those files I
actually want to rename:
$ ls -d *fuu* | sed 'p;s/fuu/bar/' | xargs -l2 echo mv
mv fuu bar
mv fuubar barbar
I can get as elaborate as I want with my filter if I use grep; here is the same as above using grep:
$ ls | grep 'fuu' | sed 'p;s/fuu/bar/' | xargs -l2 echo mv
mv fuu bar
mv fuubar barbar
Instad of using xargs you can also use a while loop; I use that variant to save me the trouble of dealing with escaping in xargs:
$ ls | grep 'fuu' | sed 'p;s/fuu/bar/' | while read a && read b; do echo mv "$a" "$b"; done
mv fuu bar
mv fuubar barbar
One last example, where we replace files recursively in the home directory:
I use find
instead of ls
which lists a directory recursively
(I am not actually running this and neither should you):
$ find | sed 'p;s/fuu/bar/' | xargs -l2 mv
...
Comments
Write a comment.