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

Saturday, May 7, 2011

Fixing double letter issues when naming people in photos in iPhoto

Problem:
When in iPhoto and naming/identifying people using "Faces" when you type in the text field all characters typed are doubled (see below)

Cause:
An address book corruption... yeah annoying hu?

Detection:
in /var/log/system.log look for lines similar to the following:
12/2/10 12:45:00 PM AddressBookSync1871 Critical error fetching ABCDContact in context <ABManagedObjectContext: 0x10012b670>: Fatal error. The database at /Users/kurtpedrosa/Library/Application Support/AddressBook/AddressBook-v22.abcddb is corrupted. SQLite error code:11, 'database disk image is malformed'
The data that's important here is:
database at Application Support/AddressBook/AddressBook-v22.abcddb is corrupted

Solution:
Rebuild you address book database.
I'm partially referencing the instructions from this site:
http://forums.macnn.com/82/applications/431074/corrupt-address-book-how-can-i/
1) Exit iPhoto & other apps that may use the address book.
2) Backup your address book.
  • Open Address book.
  • Click on "All Contacts" and drag that into a folder of your choosing. (that was easy)
  • Make another type of backup, from address book click on the "File" menu->"Export..."->"Address Book Archive..." and save that file somewhere safe too.
  • Quit Address Book
3) Fix the address book database.
Run the following code logged in as your user from the terminal:
echo '.dump' | sqlite3 ~/Library/Application\ Support/AddressBook/AddressBook-v22.abcddb | sqlite3 ~/Library/Application\ Support/AddressBook/AddressBook-v22.abcddb.new; mv ~/Library/Application\ Support/AddressBook/AddressBook-v22.abcddb ~/Library/Application\ Support/AddressBook/AddressBook-v22.abcddb.bak; mv ~/Library/Application\ Support/AddressBook/AddressBook-v22.abcddb.new ~/Library/Application\ Support/AddressBook/AddressBook-v22.abcddb

Here is the code broken into three separate commands:
echo '.dump' | sqlite3 ~/Library/Application\ Support/AddressBook/AddressBook-v22.abcddb | sqlite3 ~/Library/Application\ Support/AddressBook/AddressBook-v22.abcddb.new
mv ~/Library/Application\ Support/AddressBook/AddressBook-v22.abcddb ~/Library/Application\ Support/AddressBook/AddressBook-v22.abcddb.bak
mv ~/Library/Application\ Support/AddressBook/AddressBook-v22.abcddb.new ~/Library/Application\ Support/AddressBook/AddressBook-v22.abcddb

Start Address Book & iPhoto, enjoy tagging faces again.