Tuesday, May 17, 2011

Rename multiple files' file extensions from the command line with a wildcard

Problem:
I hate not being able to rename multiple files from the command prompt/mac terminal session, ie:
mv *.thisisthewrongextension *.thisiscorrect
or as my example will be for torrent files incorrectly downloaded by a browser as .torrent.html files:
mv *.torrent.html *.torrent
This lack in functionality in bash just drives me crazy.

Solution:
Here are two inline commands, a bash script, a pearl script, and GUI Mac program, that will overcome this shortfall. In these two command examples we will try to rename files that downloaded as myfile.torrent.html to myfile.torrent.
  1. find *.torrent.html -exec mv -vn {} `echo {} | cut -f1 -d.`.torrent \;
    find *.torrent.html -exec bash -c 'mv -vn {} $(basename {} .torrent.html).torrent' \;
  2. for i in *.torrent.html; do mv -vn "$i" "`basename $i .torrent.html`.torrent"; done
  3. bash/pearl script examples located here
  4. You could also use this GUI program on a mac, it is VERY, VERY, VERY awesome: A Better Finder Rename
Solution 1 breakdown: In the first example we will use the find command in conjunction with executing another command inline. In the second example we will be using a bash script in a single line. Let's begin. command breakdown:
find *.torrent.html
will output: 
myfile1.torrent.html
myfile2.torrent.html
myfile3.torrent.html
Then we run into the "-exec" parameter of the find command... it may seam a lot to deal wth, but basically it just runs a command each time find returns a result. Moreover it will pass the found file/directory in the form of "{}". for example:
find *.torrent.html -exec echo {} \;
Please note: at the end of an "exec" you must have whitespace(a space in the above example), then a backslash(escape character) followed by a semicolon. If you don't do that you'll get the following error:
find: -exec: no terminating ";" or "+"
anyway, the aforementioned command will output: 
myfile1.torrent.html
myfile2.torrent.html
myfile3.torrent.html
Let's take a break from the "exec" function of the "find" command, and move on to the "mv" command (pun intended). When you move or rename a file you need to specify a source & destination file/directory. Now since we can't move multiple files with a wildcard as the destination with the "mv" command (as is the point of this article) will not work
mv *.torrent.html *.torrent    -   this will not work
However, the following 3 commands will work (and this is exactly what we will be replicating in this first example):
mv myfile1.torrent.html myfile1.torrent
mv myfile2.torrent.html myfile2.torrent
mv myfile3.torrent.html myfile3.torrent
I'm also adding the "-vn" options to the "mv" command so it will be verbose about what it is moving & to prevent "mv" from overwriting an existing file. Now that we know what "mv" will be expecting, let's see how we can get "find" & "-exec" to pass the file names to the "mv" command.
find *.torrent.html -exec mv -vn {} `echo {} | cut -f1 -d.`.torrent \;
find *.torrent.html -exec bash -c 'mv -vn {} $(basename {} .torrent.html).torrent' \;

The {} portion of the above command will be replaced with "myfile1.torrent.html". For the next portion of the command we will look at the bold section in the command above. 
`echo {} | cut -f1 -d.` is translated into:
`echo myfile1.torrent.html | cut -f1 -d.` 
$(basename {} .torrent.html) is translated to:
$(basename myfile1.torrent.html .torrent.html) 
The above example will give the following output (note that anything inside the  ` (tick marks) will be executed first):
myfile1
This output still needs an extension appended to it, so we do the following:
`echo {} | cut -f1 -d.`.torrent
$(basename {} .torrent.html).torrent
And we get the following output:
myfile1.torrent
So now the "mv" command has both a source ("{}" as represented in the example below), and the destination ("`echo {} | cut -f1 -d.`.torrent" "$(basename {} .torrent.html).torrent" also represented below), and we get:
find *.torrent.html -exec mv -vn {} `echo {} | cut -f1 -d.`.torrent \;
find *.torrent.html -exec bash -c 'mv -vn {} $(basename {} .torrent.html).torrent' \;
With the following output:
myfile1.torrent.html -> myfile1.torrent
myfile2.torrent.html -> myfile2.torrent
myfile3.torrent.html -> myfile3.torrent
Phew... all done with the first example.

Solution 2 breakdown... sort of:
Now let's dissect the single line bash script:
for i in *.torrent.html; do mv -vn "$i" "`basename $i .torrent.html`.torrent"; done
Expanded below with added comments it looks like:
for i in *.torrent.html;  #(the for loop begins here)
    do
        mv -vn "$i" "`basename $i .torrent.html`.torrent";  #(here we use the mv command, as we saw above, but we use the "basename" function to split the file name)
    done  #(and then we are done)
I could go into more details, but I think that should be enough for that today. For more details about this function, and to see some other bash function examples go here.

If you need help with a bash script:
  1. Feel free to comment/mail me
  2. Visit a linux forum (www.linuxquestions.org)
  3. Take a good long look at the Advanced Bash Scripting Guide
  4. Use the "man" pages for further detailed info.
-Brian
=:~)

6 comments:

  1. i hav 114 files

    all are

    quran.php_start_limit_0
    quran.php_start_limit_1
    .....
    ....
    ...
    ..
    quran.php_start_limi_113


    how to rename all these using cmd

    ReplyDelete
  2. find *.torrent.html -exec mv -vn {} `echo {} | cut -f1 -d.`.torrent \;

    This command has a problem it will output files with extension .torrent.html.torrent

    ReplyDelete
    Replies
    1. I mean it looks like it should work and makes sense... but doesn't :(

      I am using OS X version 10.8.2

      Delete
    2. Yup... good catch. The parameter is passed after the shell execution. This should replace that:
      find *.torrent.html -exec bash -c 'mv -vn {} $(basename {} .torrent.html).torrent' \;

      I know it's a response to an old thread, so just adding for posterity.

      Delete
  3. i am very impressed with your post because this post is very beneficial for me and provide a new knowledge to me
    A Better Finder Rename Crack

    ReplyDelete
  4. Play Blackjack at a Casino! - Microgaming - Microgaming
    A classic 바카라 card game is a thrilling 바카라사이트 and engaging 토토 사이트 도메인 blackjack game at Microgaming. This fun game is poormansguidetocasinogambling now goyangfc.com available for your device!

    ReplyDelete