Showing posts with label bash. Show all posts
Showing posts with label bash. Show all posts

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
=:~)

Wednesday, November 4, 2009

Remote Mac Address Normalization Script

I almost always need to get the remote mac address of a machine on my network. When I do I use a simple bash script that I wrote.

mac.sh:
#!/bin/sh

ping -oc2 $1 > NULL
arp -n $1 | awk '{print $4}' | sed 'y/abcedf/ABCEDF/' | sed 's/^\([0-9A-F]:\)/0\1/' | sed 's/:\([0-9A-F]\):/:0\1:/g' | sed 's/:\([0-9A-F]\):/:0\1:/g' | sed 's/:\([0-9A-F]\)$/:0\1/'

exit 0


Usage:
./mac.sh [hostname or IP address]
Examples:
./mac.sh hematite
./mac.sh 10.0.1.2
Output (if your computer can successfully get an arp address):
08:00:07:1A:46:09
Output (if your computer can not ping):
[delay....]
no
Output (if you are trying to get your computers mac address):
no
Output (if you are trying to get a computer outside of your lan):
no
Output (for a machine on the same LAN but you have no route):
(inComplEtE)



Script dissected:
ping -oc2 $1 > NULL
Will try to ping whatever computer (10.0.1.2 in the example above) two times, and it will quit after receiving one ping echo response. It will not display anything to the terminal because the output is redirected to NULL.
arp -n $1
Will retrieve the layer-2 mac address from your computer's arp database, if it exists.
Output from example above:
? (10.0.1.2) at 8:0:7:1a:46:9 on en1 [ethernet]
| awk '{print $4}'
Will grab just the mac address.
Output from example above:
8:0:7:1a:46:9
| sed 'y/abcedf/ABCEDF/'
Will convert lower-case to upper-case for the letters A-F.
Output from example above:
8:0:7:1A:46:9
| sed 's/^\([0-9A-F]:\)/0\1/'
Will add a '0' in front of the first HEX value if it needs one.
Output from example above:
08:0:7:1A:46:9
| sed 's/:\([0-9A-F]\):/:0\1:/g' | sed 's/:\([0-9A-F]\):/:0\1:/g'
Will add a '0' in all other areas as needed in the mac address, except for the last HEX value. You will notice that this line is essentially one search & replace repeated two times, this is because the search finds :#:, but would skip the second HEX value ie: :#:#:.
Output from example above:
08:00:07:1A:46:9
| sed 's/:\([0-9A-F]\)$/:0\1/'
This last line will add a '0' in the final HEX value if needed.
Output from example above:
08:00:07:1A:46:09

-Brian


Further reading on sed, grep, & awk:
http://tweaksandpics.com/advanced-shell-scripting-awk-sed-and-grep/
http://www.toannguyen.com/moresed.html
More info on backreferences:
http://www.regular-expressions.info/brackets.html