11

I was looking for a way to rename a huge number of similarly-named files, much like this one (a Windows-related question) except that I'm using *nix (Ubuntu and FreeBSD, separately).

Just to sum up, while using the shell (Bash, CSH, etc.) how do I mass-rename a number of files such that, for example, the following files:

Beethoven - Fur Elise.mp3
Beethoven - Moonlight Sonata.mp3
Beethoven - Ode to Joy.mp3
Beethoven - Rage Over the Lost Penny.mp3

Will be renamed like these?

Fur Elise.mp3
Moonlight Sonata.mp3
Ode to Joy.mp3
Rage Over the Lost Penny.mp3

The reason I want to do this is that these collection of files will go under a directory named "Beethoven" (i.e. the filenames' prefix), and having this information on the filename itself will be redundant.

8 Answers 8

12

The Perl's rename utility will do what you want.

Just use rename 's/Beethoven\ -\ //g' *.mp3

4
  • Can you please edit to provide a link to the utility, or is it standard in both distributions mentioned (Ubuntu and FreeBSD)? Aug 21, 2009 at 20:46
  • 1
    It comes with Perl. Debian uses it as the rename command, so I suspect Ubuntu does too. There is another 'rename' command that is worthless, so check the man page.
    – derobert
    Aug 23, 2009 at 8:33
  • wow, the rename syntax looks very alike to vim replace :D Jun 1, 2011 at 3:25
  • the port in /usr/ports/sysutils/rename worked perfectly in FreeBSD
    – Josh W.
    Jan 9, 2012 at 23:45
10

Like so many things with Linux/Unix, there is more than one way to do it!

Some might use mmv (mass mv).

The rename, or prename command that comes with some Perl packages (newer Ubuntu and Debian) can do this as well.

prename 's/Beethoven\ -\ //g' Beethoven*.mp3

Note that the rename tool on Fedora 10 is not the same program as this and works differently. It is part of a different package, util-linux-ng and can be found here.

I often just go to the shell for loop out of habit (after all such tools didn't always exist).

mkdir -p Beethoven
for x in Beethoven\ -\ *
do
mv "$x" Beethoven/"${x/Beethoven - /}"
done

This will create the Beethoven directory, then move the file to that directory/filename, where the filename has the "Beethoven - " replaced with nothing.

As a test, before:

$ ls
Beethoven - Fur Elise.mp3                 Beethoven - Ode to Joy.mp3
Beethoven - Moonlight Sonata.mp3          Beethoven - Rage Over the Lost Penny.mp3

After:

$ ls Beethoven/
Fur Elise.mp3                 Ode to Joy.mp3
Moonlight Sonata.mp3          Rage Over the Lost Penny.mp3
4

Try this solution, implemented using a combination of grep and rename. This is assuming you have a whole bunch of directories that you need to rename. If there are just a few, I'd go with the manual approach, with the "Artist - " pattern hardcoded for each.

# Rename any mp3 files in the directory hierarchy of type "a - b.mp3" to "b.mp3"
find . -name "*.mp3" -exec rename -n -v 's|^(.*/).*-\s*(.*)\s*$|$1$2|g' {} \;

Explanation of find:
The find command finds all the files with .mp3 extension in the current file hierarchy, and calls the argument passed to -exec for each. Note that within -exec, {} refers to the argument passed by find (file path+name). The \; ensures the ; gets passed to rename and instead of indicating the end of the find command.

The command given above has the -n (no action) switch; hence it will only tell you what would happen if you ran it. I recommend you run this command without the -n switch only after you've run it once and are satisfied with the proposed results.

Now for the regexp.

  • ^ matches start of string.
  • $ matches the end of the string.
  • . matches any character.
  • * means zero or more of the preceeding construct. e.g .* means zero or more characters.
  • \s stands for any space character.
  • \S stands for any non-space character.
  • Anything within () gets captured and is reflected as $1, $2, depending on its position.

The regexp:

  • Looks for an optional directory structure at the start, captures this in $1: (.*/)
  • Skips everything till the "-" : .*-
  • Captures the rest of the string, excepting leading and trailing spaces, in $2: (.*)
  • Rewrites the file as $1$2, which should be "/path/to/file/""filename.mp3"

Here's my test run:

/tmp/test$ ls -R
.:
a  b  c  z-unknown.mp3

./a:
a3.mp3                 a - longer title3.mp3  c.mp3   disc2
a - longer title2.mp3  a - long title.mp3     disc 1  the band - the title.mp3

./a/disc 1:
a - title1.mp3  a - title2.mp3

./a/disc2:
a - title1.mp3  a - title2.mp3

./b:
cd - ab - title3.mp3  cd - title1.mp3  cd - title2.mp3  c.mp3

./c:
c-pony2.mp4  c - pony3.mp3  c - pony4.mp3  c-pony.mp3

/tmp/test$ find . -name "*.mp3" -exec rename -v 's|^(.*/).*-\s*(.*)\s*$|$1$2|g' {} \;
./c/c-pony.mp3 renamed as ./c/pony.mp3
./c/c - pony4.mp3 renamed as ./c/pony4.mp3
./c/c - pony3.mp3 renamed as ./c/pony3.mp3
./z-unknown.mp3 renamed as ./unknown.mp3
./a/the band - the title.mp3 renamed as ./a/the title.mp3
./a/a - longer title2.mp3 renamed as ./a/longer title2.mp3
./a/a - longer title3.mp3 renamed as ./a/longer title3.mp3
./a/disc 1/a - title1.mp3 renamed as ./a/disc 1/title1.mp3
./a/disc 1/a - title2.mp3 renamed as ./a/disc 1/title2.mp3
./a/a - long title.mp3 renamed as ./a/long title.mp3
./a/disc2/a - title1.mp3 renamed as ./a/disc2/title1.mp3
./a/disc2/a - title2.mp3 renamed as ./a/disc2/title2.mp3
./b/cd - title1.mp3 renamed as ./b/title1.mp3
./b/cd - title2.mp3 renamed as ./b/title2.mp3
./b/cd - ab - title3.mp3 renamed as ./b/title3.mp3

/tmp/test$ ls -R
.:
a  b  c  unknown.mp3

./a:
a3.mp3  c.mp3  disc 1  disc2  longer title2.mp3  longer title3.mp3  long title.mp3  the title.mp3

./a/disc 1:
title1.mp3  title2.mp3

./a/disc2:
title1.mp3  title2.mp3

./b:
c.mp3  title1.mp3  title2.mp3  title3.mp3

./c:
c-pony2.mp4  pony3.mp3  pony4.mp3  pony.mp3

Regexp's are tricky business, and I've made many a blunder writing them, so I'd welcome any input on the lack of merit of this one. I know I found quite a few testing this.

1

I've put together a command-line Java program that makes renaming files (and directories) a breeze, especially for scripting. It's free and open source, so you can modify it to your heart's content:

RenameWand
http://renamewand.sourceforge.net/

Some relevant usage examples:

Move files of the form "<a> - <b>" to their respective directory "<a>":

java -jar RenameWand.jar  "<a> - <b>"  "<a>/<b>"

Sort files by last-modified time, and rename them with a 3-digit number:

java -jar RenameWand.jar  "<a>"  "<3|#FT> <a>"

Rearrange parts of the filename, and change case:

java -jar RenameWand.jar  "<album> - <artist> - <song>.<ext>" 
                          "<artist.upper> <album.title> - <song>.<ext.lower>"
1

zmv The zsh renaming tool is very good.

# zmv "programmable rename"
# Replace spaces in filenames with a underline
zmv '* *' '$f:gs/ /_'
# Change the suffix from *.sh to *.pl
zmv -W '*.sh' '*.pl'
# lowercase/uppercase all files/directories
$ zmv '(*)' '${(L)1}' # lowercase
$ zmv '(*)' '${(U)1}' # uppercase

More notes here...

0

Here is a simple command-line example. I had the word "white" in the middle of the names of a bunch of png files. I wanted to take it out and leave just a single underscore. This did the trick:

for i in *.png; do mv "$i" "${i/white/}"; done
0

Use vidir and then use your editor features to apply the renaming pattern.

NAME

vidir - edit directory

SYNOPSIS

vidir [--verbose] [directory|file|-] ...

DESCRIPTION

vidir allows editing of the contents of a directory in a text editor. If no directory is specified, the current directory is edited.

When editing a directory, each item in the directory will appear on its own numbered line. These numbers are how vidir keeps track of what items are changed. Delete lines to remove files from the directory, or edit filenames to rename files. You can also switch pairs of numbers to swap filenames.

Note that if "-" is specified as the directory to edit, it reads a list of filenames from stdin and displays those for editing. Alternatively, a list of files can be specified on the command line.

EXAMPLES

vidir vidir *.jpeg Typical uses.

find | vidir - Edit subdirectory contents too. To delete subdirectories, delete all their contents and the subdirectory itself in the editor.

find -type f | vidir - Edit all files under the current directory and subdirectories.

AUTHOR

Joey Hess 2006-2010

Modifications by Magnus Woldrich 2011

COPYRIGHT

Copyright 2006-2011 the vidir "AUTHOR"s as listed above.

Licensed under the GNU GPL.

0

On a *nix without perl rename available

On some *nix there's no rename available, so it's necessary do the rename using some other method. A few years ago I suggested zmv here which while great, isn't necessarily available either.

Using builtins and sed, we can quickly whip up a shell script to do this which gives us very similar syntax to the perl rename script, while lacking some error handling niceness, it gets the job done.

sedrename() {
  if [ $# -gt 1 ]; then
    sed_pattern=$1
    shift
    for file in $(ls $@); do
      mv -v "$file" "$(sed $sed_pattern <<< $file)"
    done
  else
    echo "usage: $0 sed_pattern files..."
  fi
}

Usage

sedrename 's/Beethoven\ -\ //g' *.mp3

before:

./Beethoven - Fur Elise.mp3
./Beethoven - Moonlight Sonata.mp3
./Beethoven - Ode to Joy.mp3
./Beethoven - Rage Over the Lost Penny.mp3

after:

./Fur Elise.mp3
./Moonlight Sonata.mp3
./Ode to Joy.mp3
./Rage Over the Lost Penny.mp3

Say we wanted to create target folders too...

Since mv doesn't create folders we can't using sedrename as it is here, but it's a fairly small change, so I figure it'd be nice to include it too.

We need a utility function, abspath (or absolute path) so we can generate the target folder(s) for a sed/rename pattern which includes new folder structure.

abspath () { case "$1" in
               /*)printf "%s\n" "$1";;
               *)printf "%s\n" "$PWD/$1";;
             esac; }

This will ensure we know the names of our target folders. When we rename we'll need to use it on the target file name.

# generate the rename target
target="$(sed $sed_pattern <<< $file)"

# Use absolute path of the rename target to make target folder structure
mkdir -p "$(dirname $(abspath $target))"

# finally move the file to the target name/folders
mv -v "$file" "$target"

Here's the full folder aware script...

sedrename() {
  if [ $# -gt 1 ]; then
    sed_pattern=$1
    shift
    for file in $(ls $@); do
      target="$(sed $sed_pattern <<< $file)"
      mkdir -p "$(dirname $(abspath $target))"
      mv -v "$file" "$target"
    done
  else
    echo "usage: $0 sed_pattern files..."
  fi
}

Of course, it still works when we don't have specific target folders too.

If we wanted to put all the songs into a folder, ./Beethoven/ we can do this:

Usage

sedrename 's|Beethoven - |Beethoven/|g' *.mp3

before:

./Beethoven - Fur Elise.mp3
./Beethoven - Moonlight Sonata.mp3
./Beethoven - Ode to Joy.mp3
./Beethoven - Rage Over the Lost Penny.mp3

after:

./Beethoven/Fur Elise.mp3
./Beethoven/Moonlight Sonata.mp3
./Beethoven/Ode to Joy.mp3
./Beethoven/Rage Over the Lost Penny.mp3

Bonus round...

Using this script to move files from folders into a single folder:

Assuming we wanted to gather up all the files matched, and place them in the current folder, we can do it:

sedrename 's|.*/||' **/*.mp3

before:

./Beethoven/Fur Elise.mp3
./Beethoven/Moonlight Sonata.mp3
./Beethoven/Ode to Joy.mp3
./Beethoven/Rage Over the Lost Penny.mp3

after:

./Beethoven/ # (now empty)
./Fur Elise.mp3
./Moonlight Sonata.mp3
./Ode to Joy.mp3
./Rage Over the Lost Penny.mp3

Note on sed regex patterns

Regular sed pattern rules apply in this script, these patterns aren't PCRE (Perl Compatible Regular Expressions). You could have sed extended regular expression syntax, using either sed -r or sed -E depending on your platform.

See the POSIX compliant man re_format for a complete description of sed basic and extended regexp patterns.

You must log in to answer this question.

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