I need to rename approx. 70,000 files. For example:
From sb_606_HBO_DPM_0089000
to sb_606_dpm_0089000
etc.
The number range goes from 0089000
to 0163022
. It's only the first part of the name that needs to change. all the files are in a single directory, and are numbered sequentially (an image sequence). The numbers must remain unchanged.
When I try this in bash it grizzles at me that the 'Argument list is too long'.
Edit:
I first tried renaming a single file with mv
:
mv sb_606_HBO_DPM_0089000.dpx sb_606_dpm_0089000.dpx
Then I tried renaming a range (I learned here last week how to move a load of files, so I thought the same syntax might work for renaming the files...). I think I tried the following (or something like it):
mv sb_606_HBO_DPM_0{089000..163023}.dpx sb_606_dpm_0{089000..163023}.dpx
One way is to use
find
with-exec
, and the+
option. This constructs an argument list, but breaks the list into as many calls as needed to operate on all the files without exceeding the maximum argument list. It is suitable when all arguments will be treated the same. This is the case withrename
, though not withmv
.You may need to install Perl rename:
Then you can use, for example:
Remove
-n
after testing, to actually rename the files.I'm going to suggest three alternatives. Each is a simple single line command, but I'll provide variants for more complicated cases, mainly in case the files to process are mixed with other files in the same directrory.
mmv
I'd use the mmv command from the package of the same name:
Note that the arguments are passed as strings, so the glob expansion does not happen in the shell. The command receives exactly two arguments, and then finds corresponding files internally, without tight limits on the number of files. Also note that the command above assumes that all the files which match the first glob shall be renamed. Of course you are free to be more specific:
If you have files outside the requested number range in the same directory, you might be better off with the loop over numbers given further down in this answer. However you could also use a sequence of mmv invocations with suitable patterns:
loop over numbers
If you want to avoid installing anything, or need to select by number range avoiding matches outside this range, and you are prepared to wait for 74,023 command invocations, you could use a plain bash loop:
This works particularly well here since there are no gaps in the sequence. Otherwise you might want to check whether the source file actually exists.
Note that in contrast to
for ((i=89000; i<=163022; ++i))
the brace expansion does handle leading zeros since some Bash release a couple of years ago. Actually a change I requested, so I'm happy to see use cases for it.Further reading: Brace Expansion in the Bash info pages, particularly the part about
{x..y[..incr]}
.loop over files
Another option would be to loop over a suitable glob, instead of just looping over the integer range in question. Something like this:
Again this is one
mv
invocation per file. And again the loop is over a long list of elements, but the whole list is not passed as an argument to a subprocess, but handled internally by bash, so the limit won't cause you problems.Further reading: Shell Parameter Expansion in the Bash info pages, documenting
${parameter/pattern/string}
among others.If you wanted to restrict the number range to the one you provided, you could add a check for that:
Here
${i##pattern}
removes the longest prefix matchingpattern
from$i
. That longest prefix is defined as anything, then an underscore, then zero or more zeros. The latter is written as*(0)
which is an extended glob pattern that depends on theextglob
option being set. Removing leading zeros is important to treat the number as base 10 not base 8. The+([0-9])
in the loop argument is another extended glob, matching one or more digits, just in case you have files there that start the same but don't end in a number.One way to work around the
ARG_MAX
limit is to use the bash shell's builtinprintf
:Ex.
but
find
in current directory.
for all the files-type f
and do rename the file found$1
with replacingHBO_DPM
withdmp
one by one-exec ... \;
replace
echo
withmv
to perform rename.You can do it file by file (it may take some time) with
Like the Perl
rename
used in other answers,rename.ul
has also an option-n
or--no-act
for testing.You could write a little python script, something like:
Save that as a text file as
rename.py
in the folder the files are in, then with the terminal in that folder go:I see that nobody has invited my best friend
sed
to the party :). The followingfor
loop will accomplish your goal:There are many tools for such a job, select the one that is most understandable for you. This one is simple and easily altered to suit this or other purposes...
Since we're giving options, here's a Perl approach.
cd
into the target directory and run:Explanation
perl -e
: run the script given by-e
.foreach(glob){}
: run whatever is in the{ }
on each result of the glob.glob("sb_*")
: return a list of all files and directories in the current directory whose names match the shell globsb*
.rename $_, s/_HBO_DPM_/_dpm_/r
: perl magic.$_
is a special variable that holds each element we are iterating over (in theforeach
). So here, it will be each file found.s/_HBO_DPM_/_dpm_/
replaces the first occurrence of_HBO_DPM_
with_dpm_
. It runs on$_
by default, so it will run on each file name. The/r
means "apply this replacement to a copy of the target string (the file name) and return the modified string.rename
does what you'd expect: it renames files. So the whole thing will rename the current file name ($_
) to itself with_HBO_DPM_
replaced by_dpm_
.You could write the same thing as an expanded (and more readable script):
Depending on the kind of renaming you're envisioning, using vidir with multiple lines editing may be satisfactory.
In your particular case you could select all lines in your text editor and remove the _"HBO" part of filenames in few keystrokes.