I would like to batch rename filenames turning A-B-C#2-D.wav to A-B-C#1-D.wav. So for example:




So the number in the third substring should be decreased by one. (# is the part of filenames instead of comment; both filenames with and without # are possible.)

  • Is the third example actually...C#6? – Jeff Schaller Mar 22 at 15:25
  • 1
    No, sorry for the misleading. It is what it is to highlight they can be both with and without # – Francis Mar 22 at 15:27
  • Not a duplicate, but vim can do this relatively easy. See e.g. stackoverflow.com/questions/19195503/vim-replace-n-with-n1 – pfnuesel Mar 22 at 15:42
  • Please describe exactly the format of the file names. Is there always exactly A-B-C or should A, B, C stand for any character (except -) or for a string of variable length (without -)? Does the file name always contain exactly 3 -? Does the number always precede immediately the 3rd -? Do the numbers always consist of exactly 1 digit? If number 0 is possible, should it be replaced with -1? ... – Bodo Mar 22 at 15:56
  • If # occurs elsewhere in the filename, it should not be modified? – Kusalananda Mar 24 at 9:47

With zsh:

autoload zmv # best in ~/.zshrc
zmv -n '(*[^0-9])(<1->)(*.wav)(#qn)' '$1$(($2-1))$3'

(remove the -n if satisfied)

The (#qn) is for the list to be numerically sorted so that A-B-C#9-D.wav be renamed before A-B-C#10-D.wav for instance.

However, if there were both a A-B-C#9-D.wav and A-B-C#10-D.wav, zmv would flag the fact that one file would be renamed to the name of an existing file and abort the command. You'd need to add the -f option to still force it.

Or with zsh (for the numeric glob order) and perl's rename:

rename -n 's/\d+/$&-1/e' ./*[1-9]*.wav(#qn)

(same caveat and same -f option with some variants of rename).


With a bash-specific loop (for the regular-expression testing conditional =~):

for file in ?-?-*-?.wav
  [[ $file =~ ^([^[:digit:]]+)([[:digit:]]+)(-.\.wav)$ ]] &&
    echo mv -- "$file" "${BASH_REMATCH[1]}$((10#${BASH_REMATCH[2]} - 1))${BASH_REMATCH[3]}"

This uses a wildcard to pick up the desired wav files; matching files will have one character (letter) between the dashes and before the .wav at the end. Each filename is run through the regular expression, which separates it into three pieces:

  1. ^([^[:digit:]]+) -- the leading portion; everything up to but not including the first digit
  2. ([[:digit:]]+) -- the digit(s)
  3. (-.\.wav) -- the trailing portion

If the match succeeds, then we rename the file, using the leading portion, the digits minus one, and the trailing portion.

Remove the echo when the output looks correct.

  • Good catch; thank you Stéphane! I've tweaked the regex to be less greedy and also forced base-10 in the arithmetic. – Jeff Schaller Mar 22 at 16:34
  • well spotted again; I've simplified the regex to mirror yours and gobble up non-digits ahead of the digits – Jeff Schaller Mar 22 at 17:30
  • +1 Really nifty ! I permitted myself one small edit: when I first read your regex pattern, I just misinterpreted the bit [^[:digit:]]+ and in particular the caret in it which excludes the digit(s) from the match from the beginning of the string (=~ ^...) up to it. as a result I could not understand the fact you include the digits in the second part of the pattern matching [[:digit:]]+ and make good on it with the base-10 arithmetics on BASH_REMATCH[2]. Really nice. I'll try to write a POSIX compliant version and post it with no [[ ]] and no array... – Cbhihe Mar 24 at 8:51

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.