During importing my music files from backup (thanks to default naming scheme of rhythmbox), I have messed up the file names. Now the looks like:

00 - American Pie.ogg.ogg.ogg.ogg
00 - American Pie.ogg.ogg.ogg.ogg.ogg
00 - another brick in the wall.ogg.ogg.ogg.ogg
00 - another brick in the wall.ogg.ogg.ogg.ogg.ogg
00 - candle_in_the_wind.ogg.ogg.ogg.ogg
00 - candle_in_the_wind.ogg.ogg.ogg.ogg.ogg

While the file should look like

American Pie.ogg
another brick in the wall.ogg

And I have (as from wc -l) 3096 such files. How can I restore it in batch mode? I have tried rename and mmv to work, as given in the ans of this question, but not working (problem with the rename syntax, and for mmv,there is collision).

Any help please?

  • What should be done when there is a collision? Should the files be overwritten or do you want to keep both versions?
    – terdon
    Commented Jan 23, 2014 at 19:22
  • files should be overwritten.
    – BaRud
    Commented Jan 23, 2014 at 19:25

5 Answers 5


Using perl-rename (which is one of the two tools that is commonly called rename; the other one uses a very different syntax and can't do this in one step):

rename -f 's/00 - ([^.]*).*/$1.ogg/' *.ogg

The -f or --force option makes rename overwrite any existing files.

The second part is a perl-style regular expression substitution. The basic syntax is s/replacethis/withthis/ The pattern to match -- 00 - ([^.]*).* -- will match all the *.ogg files with names like those in your question. 00 - -- obviously, that just matches the pattern at the beginning of each of the filenames. ([^.]*) is the meat of the regular expression. [^.] will match any single character that isn't a ., while * means 'any number of the previous thing', so [^.]* means 'any number of any characters that aren't .'. The parentheses mark out a capture group (more on that in a second). In regular expressions, . means 'any character' (if you want a literal dot on this side of the substitution you have to escape it, as in: \.), so the final .* means 'any number of any character'.

In the second part of the substitution command, $1 means 'the first capture group' -- that is, that which is contained within the first pair of parentheses (see? Told you I'd come back to it). The .ogg means a literal '.ogg' -- on this side of the substitution, you don't need to escape the dot.

So, roughly translated into English, 's/00 - ([^.]*).*/$1.ogg/' is telling rename to 'take "00 - ", followed by (any number of characters that aren't a dot), then any number of characters; and replace that with the characters contained within the brackets and ".ogg."'.

On some systems, perl-rename is called prename (when rename is taken by the aforementioned other program). On some systems it isn't available at all :(

For recursiveness, you can do one of the following:

shopt -s globstar ## assuming your shell is bash
rename 's/00 - ([^.]*).*/$1.ogg/' **/*.ogg


find . -name '*.ogg' -exec rename 's/00 - ([^.]*).*/$1.ogg/' {} +

This should work:

$ for f in *ogg; do 
   mv "$f" "$(printf "%s" "$f" | perl -lpe 's/^\d+ - (.*?).ogg.*/$1.ogg/')";

To do this for all files, including those in subdirectories, either activate globstar if you're using bash:

$ shopt -s globstar
$ for f in **/*ogg; do 
   mv "$f" "$(printf "%s" "$f" | perl -lpe 's/^\d+ - (.*?).ogg.*/$1.ogg/')";


The funky stuff is being handled by perl. It is a simple regular expression that removes all numbers (\d+) followed by - (that's a dash with space on either side) from the beginning of the input string, matches everything until the fist .ogg and replaces eveything after that with the matched string ($1) and .ogg.

This could be expanded to the more cumbersome but more readable:

for f in *ogg; do 
   newname=$(printf "%s" "$f" | perl -lpe 's/^\d+ - (.*?).ogg.*/$1.ogg/')";
   mv "$f" "$newname";
  • so, we have to remove 00- bit seperately, right?
    – BaRud
    Commented Jan 23, 2014 at 19:52
  • @BaRud sorry, I completely missed that you wanted to remove that as well. See updated answer.
    – terdon
    Commented Jan 23, 2014 at 20:02

A Bourne-compatible shell can do this without Perl or sed (but its aesthetic appeal is questionable...)

for f in *.ogg
    newfile="${f#00 - }"          ## Strip the leading "00 - "
    newfile="${newfile%%.ogg*}"   ## Strip .ogg and everything after
    newfile="${newfile}.ogg"      ## Append .ogg
    mv "$f" "$newfile"            ## Do the rename

Note that is also possible to use ls -1 | while read f to create the loop, but this construct would break in the event it encountered a filename with a newline character. Using the shell's standard globbing behaviour guards against this problem.

  • Why not for f in *.ogg rather than ls -1 | while read f? Apart from that, this is a good answer, probably what I'd do if my system didn't have (perl-)rename.
    – evilsoup
    Commented Jan 24, 2014 at 12:02
  • Mainly force of habit. I don't think there's any difference in behaviour in this case.
    – D_Bye
    Commented Jan 24, 2014 at 12:06
  • I think there is a difference on those edge cases where you have newlines in filenames. There's also probably a performance difference from that pipe, but that's not worth worrying about in practice.
    – evilsoup
    Commented Jan 24, 2014 at 12:11
  • As I said - no difference in this case. But yes, ls -1 | while read f would break on really funky filenames. I'll modify the answer to use the safer for f in *.ogg
    – D_Bye
    Commented Jan 24, 2014 at 12:17

That's where Midnight Commander comes handy. Select the files with "+" or "Insert". Press F6 for rename. Set mask to *.ogg.ogg.ogg and the destination to * and press Enter. Sure, it's not automated, but it's still "in batch".

  • 1
    A typical #russianhacker :) Привет, Павел! Along those lines: krename. Commented Sep 4, 2019 at 12:43
ls -1|while read f; do 
    newfile=$(echo $f|sed -e 's/\(\.ogg\)*$/.ogg/')
    mv "$f" "$newfile"

You must log in to answer this question.

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