Consider this list of files:

$ touch index-{1,2,3,4,5,6,7,8,9}.txt

If I want to shift them down so they start at zero, it's relatively easy:

$ rename --verbose 's/^index-([1-9])\.txt$/$1/; $_="index-" . ($_ - 1) . ".txt"' index-*.txt

This works because man bash specifies that globs are alphabetically sorted, so it will rename index-1.txt to index-0.txt before renaming index-2.txt to index-1.txt.

This breaks down if you want to shift up, or if the numbers have different lengths:

$ touch index-{10,11}.txt
$ rename --verbose 's/^index-([0-9]+)\.txt$/$1/; $_="index-" . ($_ + 1) . ".txt"' index-*.txt
index-10.txt not renamed: index-11.txt already exists
index-11.txt renamed as index-12.txt
index-1.txt not renamed: index-2.txt already exists

Possible long-term fixes:

  • A rename option to try to reorder operations until there are no collisions.
  • A rename option to move the files to a temporary directory first, and then move them back with the new name.
  • A rename option to rename files in two steps, using unique names in the first step which won't collide with the original or new names.
  • A way to do natural sorting + reversal of Bash globs.

Is there a simple way to get around this?

  • unix.stackexchange.com/questions/27382/… if you're not averse to running rename in a loop. (You could reverse the list obtained in a loop if you want a single rename call I guess.) – Mat Sep 22 '12 at 10:12
  • @Mat No problem, but I was hoping to do it in less than four lines of code (not counting ; trickery) – l0b0 Sep 22 '12 at 10:26

@l0b0's solution rewritten for better robustness:

printf '%s\0' index-*.txt |
  sort --zero-terminated --field-separator - --key 2rn |
  xargs -0r rename --verbose '
    $_="index-" . ($_ + 1) . ".txt"'

Feel free to include in your solution and I'll delete my answer afterward.

Note that that and @l0bo's solutions are GNU specific, not Unix (GNU's Not Unix).

  • If you add a -- parameter at the end and tweak the regex it actually works for $'--$`!*@\a\b\E\f\r\t\v\\\'"\360\240\202\211-'{1,2,3,4,5,6,7,8,9,10,11}$'\n' – l0b0 Sep 24 '12 at 14:55

There's a little known standard command on Unix that can help us here find a general solution in finding in which order a series of renames have to be done: tsort

Say we have a list of renames to be done in a file called renames.txt (assuming for sake of demonstration that their name doesn't contain blanks):

d a
e f
b e
a c

Because d is to be renamed to a, that means a must be renamed before d. So we've got a partial sort order there which would be the reverse of the order the files should be renamed.

tsort is the tool to infer a full sort order from a list of partial sort orders. It would return with an error if there was a loop which would help us detect cases where there's no solution. If we apply tsort on that input, it gives us:


Which says b should be renamed after d after e. We can use GNU tac (some systems also have tail -r) to reverse that order:


And join it with our list of renames:

tsort renames.txt | tac | awk '
  NR==FNR {
    ren[$1] = $2
  $1 in ren {
    print "mv -i --", $1, ren[$1]
  }' renames.txt -

which gives us:

mv -i -- a c
mv -i -- e f
mv -i -- d a
mv -i -- b e

which we can then pipe to sh to execute.

Note however that the above code is not robust in that we didn't check the exit status of tsort above to detect loops, and filenames mustn't contain any special shell characters.

The robustification is left as an exercise to the reader ;-)


This works for the specific case above:

rename --verbose 's/^index-([0-9]+)\.txt$/$1/; $_="index-" . ($_ + 1) . ".txt"' $(printf '%s\n' index-*.txt | sort --field-separator - --key 2n | tac)


  1. it doesn't handle whitespace in filenames, and
  2. it would be much more complicated for arbitrary filenames.

Not a direct answer to your question as you're mentioning bash, but with zsh, you can specify the sort order and numerical sort with globbing qualifiers.

$ echo index-*.txt
index-12.txt index-1.txt index-2.txt index-4.txt
$ echo index-*.txt(n)
index-1.txt index-2.txt index-4.txt index-12.txt
$ echo index-*.txt(nOn)
index-12.txt index-4.txt index-2.txt index-1.txt

Or you can define your own sort order with a function and use index-*(o+that-function).

Also note that zsh has its own rename builtin function called zmv:

$ zmv -fvQ 'index-(<->).txt(n)' 'index-$(($1-1)).txt'
mv -- index-1.txt index-0.txt
mv -- index-2.txt index-1.txt
mv -- index-4.txt index-3.txt
mv -- index-12.txt index-11.txt

$ zmv -fvQ 'index-(<->).txt(nOn)' 'index-$(($1+1)).txt'
mv -- index-11.txt index-12.txt
mv -- index-3.txt index-4.txt
mv -- index-1.txt index-2.txt
mv -- index-0.txt index-1.txt
  • I'm looking for a Bash solution, as indicated by the tags. – l0b0 Sep 24 '12 at 14:45

For context i use LapseIt Pro App to timelapse stuff & for some reason it stopped , i then restarted the timelapse but it gave it a different project name like :-


I needed to rename all the 3000'ish files to a previous project Proj9_img0000000x & to amend the number at the end so the project had a continuous Name+Number feed so i could render all the images as one project video for example

Proj10_img00000001 --> Proj10_img00003249 needed to change to Proj9_img00004325 --> Proj9_img00007574

So all in all the end result looks like this in the project folder Proj9_img00000001 --> Proj9_img00007574

If using Mac OSX ( i am using Mojave ) you can

  1. In finder highlight all files to amend
  2. Right click on a highlighted file section
  3. Select Rename 'X amount' Items
  4. A "Rename Finder Items" popup window opens
  5. This popup window allows you to rename & renumber easily as well as a few other options which is handy.

I am still looking at command line grep/sed/awk/cut & find commands but for now the above method works ( i needed the video done asap for posting )

  • This is an OS X specific solution, and I'm looking specifically for a Bash solution, as tagged. – l0b0 Apr 30 '19 at 10:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.