Take the 2-minute tour ×
Ask Different is a question and answer site for power users of Apple hardware and software. It's 100% free, no registration required.

I do a fair amount of work in the command line, and I find myself defining a lot of aliases of the form:

alias skim='/Applications/Skim.app/Contents/MacOS/Skim'

Is there a way to add magic such that asking for executable "foo" automatically uses executable /Applications/Foo.app/Contents/MacOS/Foo? This is on OS X 10.6.

(I'm aware that I could do "open foo.pdf", but Skim is not the default PDF reader, and I'd like a general solution - in some cases, it's not appropriate to set the application in question as the default handler for the file.)

share|improve this question
16  
You can open -a Skim foo.pdf. –  user588 Nov 23 '10 at 0:54
1  
@mankoff: nice, I forgot you could use -a to tell open that you want an app from the Applications dir. you should change this to an answer :) –  Robert S Ciaccio Nov 25 '10 at 6:26
    
@Reid: could you edit your question to include an example command line that you are looking to be able to type? –  Robert S Ciaccio Nov 25 '10 at 6:28
    
@mankoff which means he needs aliases for sure or extension of tab completion –  Robert S Ciaccio Nov 25 '10 at 7:01
    
@Reid any issues with the solution? –  user588 Jan 22 '11 at 16:02
show 1 more comment

4 Answers

up vote 8 down vote accepted

Finally I got it: Add this to your .bash_profile

function foomagick() {
    rm -f ~/.foomagick.tmp
    ls /Applications/ | grep "\.app" | grep -v iWork | while read APP; do
        # clean it up                                                           
        a=`echo $APP | sed s/\ //g`;
        a=`echo $a | sed s/\'//g`;
        echo alias ${a%.*}="'open -a \"${APP%.*}\"'" >> ~/.foomagick.tmp
    done
    source ~/.foomagick.tmp
    rm ~/.foomagick.tmp  
}
foomagick()

Now the following work:

Skim # open Skim.app
Skim foo.pdf
FireFox http://google.com
FireFox google.com # ERROR. Looks for local file.

Edit by Reid:

I implemented the above as a Python script that makes wrapper scripts instead of aliases. You will need to put ~/bin/mankoffmagic in your path. If you want the wrappers to be updated automatically, run it regularly from cron or somesuch.

#!/usr/bin/python
#
# This script automagically updates a set of wrapper shell scripts in
# ~/bin/mankoffmagic which call Mac apps installed in /Applications.
#
# Inspired by mankoff's shell alias posted on apple.stackexchange.com; see:
# http://apple.stackexchange.com/questions/4240/concisely-starting-mac-os-apps-from-the-command-line/4257#4257
#
# Notes/Bugs:
#
# 1. Does not follow symlinks (aliases?)
#
# 2. Assumes that application names do not contain double-quotes.
#
# 3. Not very smart about finding the actual binary (it guesses). This is
# wrong sometimes, e.g. Firefox. Probably a deeper understanding of the app
# package structure would fix this.

import copy
import glob
import os
import os.path
import re

BINDIR = os.path.expandvars("$HOME/bin/mankoffmagic")
APP_RE = re.compile(r'(.*)\.app$')
STRIP_RE = re.compile(r'[\W_]+')

def main():
   # We aggressively delete everything already in BINDIR, to save the trouble
   # of analyzing what should stay
   for f in glob.glob("%s/*" % BINDIR):
      os.unlink(f)

   # Walk /Applications and create a wrapper shell script for each .app dir
   for (root, dirs, files) in os.walk("/Applications"):
      dirs_real = copy.copy(dirs)  # so we can manipulate dirs while looping
      for d in dirs_real:
         #print "checking %s" % os.path.join(root, d)
         m = APP_RE.search(d)
         if (m is not None):
            #print "Found " + m.group()
            dirs.remove(d)  # no need to recurse into app
            create_script(root, d, m.group(1))

def create_script(path, appdir, appname):
   # remove non-alphanumerics and downcase it
   wrapper = STRIP_RE.sub('', appname).lower()
   wrapper = os.path.join(BINDIR, wrapper)
   fp = open(wrapper, "w")
   # Twiddle the comments in the script depending on whether you want to
   # invoke the binary or use "open" -- the former lets you use any
   # command-line args, while the latter is more Mac-like (app puts itself in
   # the front, etc.)
   fp.write("""
#!/bin/sh
exec "%s/%s/Contents/MacOS/%s" "$@"
#open -a "%s" "$@"
""" % (path, appdir, appname, appname))
   fp.close()
   os.chmod(wrapper, 0700)


if (__name__ == "__main__"):
   main()
share|improve this answer
    
The "grep -v iWork" is in there just because the apostrophe in "iWork '09" is messing things up. Note that if you store apps elsewhere (~/local/Applications) just add that to the ls command, and this will work there too. –  user588 Nov 25 '10 at 6:58
    
nice one. i like that it's dynamic so you don't end up with any maintenance issues like cleaning up old aliases. –  Robert S Ciaccio Nov 25 '10 at 7:04
    
Thanks @mankoff! Smells great - once I've tested it and it works I'll mark the answer accepted. Much appreciated. –  Reid Dec 13 '10 at 0:25
    
OK, this works. It does break on apps with an apostrophe (and presumably other funny chars in the name). I riffed on your solution and did something similar in Python, which I'll post in another answer - though if that's not the right etiquette, I'm happy to add it to your answer instead (or whatever). –  Reid Jan 28 '11 at 17:57
    
Answer added here per feedback. Thanks again! –  Reid Feb 1 '11 at 21:17
add comment

You don't need anything fancy, you already have the answer. Try:

open /Applications/Foo.app bar.pdf

on edit

In light of the comments below, I think my answer would still be relatively similar... I would make a function that overrides open, does a pushd to /Applications, calls /usr/bin/open $appName.app $args, does a popd and returns.

I suck at shell scripting but something like below which covers special cases and keeps you using pretty much the same syntax as apple provided for open. I like to keep my environment as clean as possible.

I'm sure the syntax is from outer space:

function open($appName, $args)
{
    #if we are calling open without a path like /Applications/TextEdit.app AND
    #    $appName ends in '.app'
    if (!hasParent($appName) && isApp($appName))
    {
        pushd /Applications
        /usr/bin/open ${appName}.app $args &
        popd
        return
    }
    #otherwise, use open as normal
    /usr/bin/open $appName $args
    return
}

on second edit

looking @mankoff's comment on the original question, most of the stuff in the above function would probably be a waste of time since you could just use open -a $appName. So mankoff probably has the easiest solution and should change his comment to an answer ;)

share|improve this answer
    
I think @Reid wants a shorter way w/o manually creating all the aliases. –  user588 Nov 23 '10 at 5:47
    
But he doesn't have to create an alias to the unix executable if he uses 'open' on the .app package. I got the impression that he was creating aliases because he thought the only way to start an app directly from the command line was by going all the way into the MacOS directory and executing the file that is the 'real' executable inside an app. So in that case, an alias would at least save typing the extra three levels of directory structure that you would need in order to start the app. Maybe I'm misunderstanding his question. –  Robert S Ciaccio Nov 23 '10 at 5:54
    
@calevara, thanks - @mankoff is right; it's about length. –  Reid Nov 23 '10 at 16:48
3  
This is a bit of a misuse of the edit functionality. If you later would like to propose a second answer, do just that. Do not edit your original answer, piggybacking the votes that it received onto your new idea. –  Jeff Swensen Nov 24 '10 at 14:00
1  
I don't agree... the answer is still pretty similar because he'd be using the same syntax. Not only that, but if up-voters don't like the new answer they can change their vote because the answer has been edited. That's exactly why you can change your vote after edits. It's also why I put "on edit" in bold. –  Robert S Ciaccio Nov 24 '10 at 18:37
show 3 more comments

There are two solutions that I can think of:

The easier way - Using the Info.plist file in each .app Contents directory, create an index of the value for the keys CFBundleExecutable. Then add a short alias that calls a script (perl, python, whatever) with the name of an executable and the arguments you'd like to pass.

Your index would be pairs of executable names and paths to those executables. You'd have to write a recurring scheduled script to keep this updated.

You'd end up being able to call:

f foo file.txt

where f is an alias to a script that checks your index for an executable named foo.

All in all, not a lot of work to get the functionality you'd like.

The harder way - Extend your shell to supplement the handling of its command_not_found error. Essentially you would implement a Ruby style method_missing functionality within whatever shell you're using. When command_not_found was thrown, your method would check the executable names of your installed Applications.

share|improve this answer
add comment

Applescript to the rescue:

osascript -e 'tell application "iTunes"' -e "activate" -e 'end tell'

Replace the name of the application with the application you want to start and you're done. You can of course make it a shell function if needed:

function f() {
    osascript <<EOF
tell application "$1"
  activate
end tell
EOF
}

and use it that way:

f iTunes

I hate applescript, but it is useful sometimes, and I believe the only way to address an application simply by name on the command line. Everything else will require a full path.

share|improve this answer
    
Doesn't support tab-completion. Requires remembering app names exactly. Don't see why AppleScript is required (especially if you hate it) if shell scripting can do it all. –  user588 Nov 25 '10 at 6:57
1  
I never realized that open -a could be used without a file name as argument. And I've been using open since NextStep sometimes in the early 90's! Please submit that as an answer so I can vote on it. So yes, the correct answer is to use 'open -a <application name>' –  eric Nov 25 '10 at 12:16
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.