Batch Renaming Using sed

I was reorganizing my music library and decided to change the naming convention I’ve used. This task is just asking to be automated. Since the filename change could be described using regular expression, I looked for a way to use sed for the renaming process.

The files I had, had the following pattern as filename ARTIST – SONG – TRACK – ALBUM

James Brown - I Got You (I Feel Good).ogg  - 01 - Classic James Brown

I wanted to rename it to ARTIST – ALBUM – TRACK – NAME

James Brown - Classic James Brown - 01 - I Got You (I Feel Good).ogg

Describing the change as a sed program is easy:

s/\(.*\) - \(.*\) - \(.*\) - \(.*\).ogg/\1 - \4 - \3 - \2.ogg/

Now all that has to be done is to pass each filename to mv and pass it again after it went through the sed script. This could be done like this:

for i in *; do
  mv "$i" "`echo $i | sed "s/\(.*\) - \(.*\) - \(.*\) - \(.*\).ogg/\1 - \4 - \3 - \2.ogg/"`";
done

The important part is the

`echo $i | sed "s/\(.*\) - \(.*\) - \(.*\) - \(.*\).ogg/\1 - \4 - \3 - \2.ogg/"`

which pipes the filename to sed and returns it as an argument for mv.

To see what renaming will be done one can alter a bit the above command, and get

for i in *; do
  echo "$i" "->" "`echo $i | sed "s/\(.*\) - \(.*\) - \(.*\) - \(.*\).ogg/\1 - \4 - \3 - \2.ogg/"`";
done

While will effectively print a list of lines of the form oldname -> newname.

Of course this technique isn’t limited to the renaming I’ve done. By changing the pattern given to sed, one can do any kind of renaming that can be described as a regular expression replacement. Also one can change the globbing (the *) in the for loop to operate only on specific files, that match a given pattern, in the directory instead of all of them.

10 thoughts on “Batch Renaming Using sed

  1. Thanks Guy for the inspiration this page gave me.

    I had no experience of sed or awk but quickly got a sed solution to almost solve my problem and then discovered that arithmetic is not easy with sed.

    After much playing the following worked:

    cd /test; find . -name "ts*" | awk -F, '{ print "mv *"$3","$4" "$3 -1000"-"$4 -1000".png" }' | sh
    

    This changes the names of all files below /test which start with ‘ts’. My file names were 137 characters long and included many special characters. They ended with comma separated data which I wanted to retain and decrease by 1000. My solution separates the data with a hyphen and adds an extension. Whilst restricting ‘find’ to files starting with ‘ts’ is not necessary it ensures directory names are not included (unless they start with ts!) and prevents the modified names being modified again – these may not happen but I think this simple restriction is a wise precaution.

  2. oops – for “increase” read ‘decrease’.

    Guy – perhaps you could edit this for me and format the code.

  3. Done, I’m glad it has been an inspiration. I’m aware of awk’s advantages over sed, but I just didn’t find the time, yet, to learn it properly. Sed is much simpler.

  4. Hello Guy,

    it seems the you have quite some batch file programming experience! 🙂

    Is it possible to replace “” with ” using sed?
    (double quote replaced with single qoute)
    I like to turn “”path”” to “path”.

    Or do you know a side with a documentation for sed?

  5. Hello Guy, thank you for you answer.

    No I haven’t. I larked around with “FOR /F” until I read about sed on the web. Then you found your blog.
    But I am not sure anymore, if sed is helping me further, since I am on Windows…

    I understood, that sed is a Unix specific command. Is this correct? Do you know of an equivalent on Windows?

    Thanks again!

  6. sed is indeed a unix command, so it doesn’t exist natively in Windows. You can always intall something like cygwin which gives you a unix environment inside your windows. Also I’m pretty sure that searching for “sed for windows” would turn up useful results.

  7. Coming back to the issue, I’m feeling python is more suitable. A bit longer but safer (no shell escaping problems).

    import os
    import re
    
    [os.rename(s,re.sub(r"(\d{4})(\d{2})",r"\1-\2",s)) for s in os.listdir('.')]
    

    The example above separates year-month dates using a dash (201201 -> 2012-01).

  8. Great article, which does nearly what I want. I’m trying the 2nd echo version rather than rename (mv) version. The displayed output is always i$, so it doesn’t seem to pipe into sed correctly. I’m relatively new to linux and sed and struggling to understand the various quotes and double quotes used, so not sure what’s going wrong.

  9. Thanks for the article, it really helped me!

    May I suggest a typo correction?

    The files I had, had the following pattern as filename ARTIST – song – TRACK – ALBUM
    …
    I wanted to rename it to ARTIST – ALBUM – TRACK – name

    For consistency and to avoid confusion, the asterisked words should be uniform and share the same nomenclature. Perhaps both should say “SONG” rather than “NAME”.

Leave a Reply

Your email address will not be published. Required fields are marked *