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
    Jan 23 '14 at 19:22
  • files should be overwritten.
    – BaRud
    Jan 23 '14 at 19:25

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
    Jan 23 '14 at 19:52
  • @BaRud sorry, I completely missed that you wanted to remove that as well. See updated answer.
    – terdon
    Jan 23 '14 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
    Jan 24 '14 at 12:02
  • Mainly force of habit. I don't think there's any difference in behaviour in this case.
    – D_Bye
    Jan 24 '14 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
    Jan 24 '14 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
    Jan 24 '14 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. Sep 4 '19 at 12:43
ls -1|while read f; do 
    newfile=$(echo $f|sed -e 's/\(\.ogg\)*$/.ogg/')
    mv "$f" "$newfile"

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.