Grabbing a Mac app’s icon: advanced Bash usage

[Tweet : nvALT]

In the previous post in this series, we looked at the basic Terminal commands we’d use to grab a Mac application’s icon from the command line. In this post, we’ll flesh out the script a little and turn it into a Bash function with some added features:

  • Logic to locate the app if it exists outside of /Applications
  • A check using sips to find the maximum available image size
  • Allow user input to set the final output size
  • Allow the user to decide whether to open the result in Preview.app

We’ll also look at a very, very cool trick for adding tab-completion for application names to the open -a command, as well as our geticon() function. Yes, it’s that nerdy.

In the next post, we’ll use Automator to make this into a droplet we can drag apps onto from Finder. If you don’t give a flying fire truck about Terminal, you can skip straight there and create something useful without touching the command line. This one’s for the nerds (and wannabe nerds).

We’ll use a bash for loop to search some predefined locations. You could also use mdfind1 to let Spotlight locate the app, but we’ll keep it simple for now. I’ve set it up to look in some standard locations for the file:

APPDIR=''
 for dir in "/Applications/" "/Applications/Utilities/" "/Developer/Applications/" "/Developer/Applications/Utilties/"; do
   if [[ -d ${dir}$APP.app ]]; then 
       APPDIR=$dir
       break
   fi
 done

$APPDIR is now set to the location that $APP was found, or to ‘’ (blank) if it wasn’t found in any of the specified folders. We can check for that in the next part and fail gracefully if we didn’t find the specified app.

Getting the maximum image size

Modern Mac icons generally have a pixel width of 512 or greater, but some legacy applications’ icons are 256px or smaller. Finding the maximum pixel width of the image allows us to avoid creating a distorted image as a result of sizing up beyond the largest image in the .icns file. The sips command can do this with a little help from awk2 to clean up the output. The following command gets the pixelWidth property from the icon file, grabs the last line of output and removes extraneous text to leave us with just a number:

MAXAVAIL=`sips -g pixelWidth "${APPDIR}$APP.app/Contents/Resources/$ICON.icns"|tail -1|awk '{print $2}'`

We could use the number we found to output a file with the maximum dimensions, but since we’re making a general-purpose function, we’ll ask the user what they want.

Getting user input

There are a few ways to offer options in Bash. This is the simplest route I know. It doesn’t innately allow for a lot of error checking; the user could enter letters instead of numbers or an unattainable dimension, for example. We’ll add some basic checks, but we’ll assume that you can properly enter a number when asked. You’re smart like that.

echo -n "Enter max pixel width ($MAXAVAIL): "
read MAX
if [[ $MAX == '' || $MAX -gt $MAXAVAIL ]]; then
  MAX=$MAXAVAIL
fi

We echo the prompt (the -n keeps it from echoing a newline), and we use our previously determined $MAXAVAIL variable to set a cap. If the user’s answer is empty or larger than $MAXAVAIL, we default to $MAXAVAIL.

The whole shebang

Here’s a complete Bash function that you can paste at the end of your ~/.bash_profile. It offers to open the resulting image in Preview.app, but you can easily modify that to whatever app makes sense in your workflow.

function geticon() {
  APP=`echo $1|sed -e 's/\.app$//'`
  APPDIR=''
  for dir in "/Applications/" "/Applications/Utilities/" "/Developer/Applications/" "/Developer/Applications/Utilties/"; do
    if [[ -d ${dir}$APP.app ]]; then 
        APPDIR=$dir
        break
    fi
  done
  if [[ $APPDIR == '' ]]; then
    echo "App not found"
  else
    ICON=`defaults read "${APPDIR}$APP.app/Contents/Info" CFBundleIconFile|sed -e 's/\.icns$//'`
    OUTFILE="$HOME/Desktop/${APP}_icon.jpg"
    MAXAVAIL=`sips -g pixelWidth "${APPDIR}$APP.app/Contents/Resources/$ICON.icns"|tail -1|awk '{print $2}'`
    echo -n "Enter max pixel width ($MAXAVAIL): "
  	read MAX
  	if [[ $MAX == ''  || $MAX -gt $MAXAVAIL ]]; then
  	  MAX=$MAXAVAIL
  	fi
    /usr/bin/sips -s format jpeg --resampleHeightWidthMax $MAX "${APPDIR}$APP.app/Contents/Resources/$ICON.icns" --out "$OUTFILE" > /dev/null 2>&1
    echo "Wrote JPEG to $OUTFILE."
  	echo -n 'Open in Preview? (y/N): '
  	read ANSWER
  	if [[ $ANSWER == 'y' ]]; then
  	  open -a "Preview.app" "$OUTFILE"
  	fi
  fi
}

Extra credit: tab completion for app names

Before we get into making this into a GUI using Automator, wouldn’t it be cool if you could autocomplete an app name from the command line when you use this? This is where you find out that I can nerd anything to death, but let’s do it.

First, we need some functions to build a list of all of our available applications and provide them to Bash’s completion command. I’m modifying a script from Kim Holburn for this, updated to work on 10.6 and provide case-insensitive completion. Just put the file in your user’s home directory for now. When we’re done, it will also provide application name tab completion for open -a (and o if you alias o=”open -a” in your .bash_profile). Click the link below to open the script, then save it to your home folder as “.app_completions”.

Download .app_completions

Once you have that file located in ~/.app_completions, we just need to reference it in .bash_profile and add a few settings. Put this near the top of ~/.bash_profile:

source ~/.app_completions
bind "set completion-ignore-case on"
set show-all-if-ambiguous on
alias o="open -a"

At the command line, run source ~/.bash_profile. Now, assuming you’ve also added the previous geticon function to .bash_profile, you should be able to type geticon term and hit tab right after the ‘m’ to have it complete to “Terminal.app” automatically. This makes it a lot easier to get the app’s name, spacing and capitalization exactly right. You’ve also aliased “o” to launch an app in the process. Type o auto to complete to “Automator.app” and launch it.

We’ll dig into Automator next and turn this whole thing into something pretty.