Let's suppose I have five mp3 files:


Now I give a quick listen to the files (just mplayer on console will do the trick) and I find out that the tracks are in wrong order. 05 is actually 02 and the rest will have to be renamed. So first I will do a temp rename:



Now we need a "shift": 02 should become 03, 03 should become 04 and 04 should become 05. To minimize confusion, ex-05 (now 00) will just be mved later.

My approach was this: (perl rename, by Larry Wall, default here on Debian)

rename 's/0([2-4])([\s\S]+)/0($1+1)$2/' *

as well as (later, after some more RTFM'ing)

rename 's/0([2-4])([\s\S]+)/0($1+1)$2/e' *

None of them worked, especially because the /e[val] modifier does not accept anything else but evaluations, and will throw an error once you attempt to combine the evaluations with strings. The bash can do it just fine, e. g. foo$((1+6)) will be evaluated to foo7.

So how can I do this (one-liner preferred, not intending to write a whole standalone script just for this)?

2 Answers 2


You almost had it. You just need to make literal strings and string concatenation explicit within the /e-modified substitution, using quotes and the dot operator.

rename 's/0([2-4])([\s\S]+)/"0".($1+1).$2/e' *
  • It leaves the original 05 still un-renamed, but +1 for resolving the one-liner glitch.
    – Peter.O
    Sep 3, 2012 at 8:16
  • 1
    (minor note, rename is called prename in Ubuntu) ... If no names in the directory starts with $'\x01'2 then a preliminary renaming like this: prename 's/^05/\x012/' 05* .. then the above [2-4] rename .. then a final prename 's/^\x01/0/' $'\x01'2* will do the trick.. That makes 3 one-liners... For a fourth, and an initial check for the possible presence of a file starting with $'\x01', something like this bash snippet will trap it .. shopt -s nullglob; x=($'\x01'*); [[ -n $x ]] && { echo '\x01' is not suitable; exit; }
    – Peter.O
    Sep 3, 2012 at 9:13
  • YEEES! Thank you Alan, I didn't think of that dot operator, though I did know what to do in human-readable words: "tell /e not to touch the stuff outside the ( )" :) And thanks to Peter for the bash approach as well. Besides, if I had had any idea of how close PHP syntax is to Perl's, I would probably have found the solution by trial-and error. Because that dot IS the JavaScript '+' counterpart in PHP for string concatenation. Sep 3, 2012 at 10:37

one-liner preferred, not intending to write a whole standalone script just for this

When things get so complicated, I don't see any reason not to write a script. You're never going to remember how to do this from one run to the next, so you're going to end up either reinventing it each time, or wrapping it up in a script anyway.

For a small thing like this, I generally start trying to solve it in Bash:

if [ -z "$1" ] ; then echo Need arguments. ; exit 1 ; fi

typeset -i i=1
for f in "$*"
    tailbits=`echo "$f" | sed -e 's/^[0-9]+//'`
    mv "$f" sertmp-`printf %02d $i`"$tailbits"

for f in "sertmp-*"
    mv "$f" `echo "$f" | sed -e s/^sertmp-//` 

Basically, this script strips off any leading digits, then puts a zero-padded increasing serial number on the front, with the files numbered according to the order you pass them to the script.

It does this in two stages, with sertmp- prefixes for the first pass to avoid any risk of name collisions. If you call this script mp3-renamer and call it like:

$ mp3-renamer 01-foo.mp3 03-bar.mp3 04-qux.mp3

you run into a trivial collision on the first rename if you don't use 2 passes to do the renaming. (01-foo.mp3 -> 01-foo.mp3.)

If you call it like this, though:

$ mp3-renamer 02-foo.mp3 01-foo.mp3

you accidentally erase 01-foo.mp3 in the first rename with a 1-pass rename.

If the problem gets more complicated, I'd rewrite it in Perl. At that point, you could then use a hash to hold the old->new name mapping, and use a bit of clever code to work out the proper order to do the renamings in order to avoid the need for 2 passes.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .