Submit Hint Search The Forums LinksStatsPollsHeadlinesRSS
14,000 hints and counting!

Create a Tab-completion-capable GUI app launcher UNIX
Inspired by the last comment on this hint, I created the ultimate (to me, at least) command-line application launcher.

Using my solution, I can now launch GUI apps from the command line, complete with Tab completion. For instance, I type iMov[Tab][Enter], and iMovie HD launches; iTu[Tab][Return] for iTunes, etc.

Here's how I did it. First, I created an Apps directory in my user's home directory, and added it to the $PATH variable. Then I placed the following script in the new Apps directory. I named it generatelinks.sh and made it executable with chmod a+x generatelinks.sh:
#!/bin/sh

cd ~/Apps
find -E /Applications/ -regex ".*\.app$" -print |
while read APPS; do
  APPLINK=`echo -n "$APPS" | sed s:".*/":"": | sed s:" ":"__":g`
  echo $APPLINK
  ln -s open.sh "$APPLINK"
done
I then ran the script, and it generated a link for each application in /Applications to the following script in the same Apps directory. This one is named open.sh, and again, remember to chomd a+x open.sh to make it executable:
#!/bin/sh

APP=`echo -n $0 | sed s:"__":" ":g | sed s:".*/":"": | sed s:".app":"": `

echo $APP $1
open -a "$APP" $1 $2 $3
Once the generatelinks.sh script has run, you're done -- you can now launch anything in /Applications by just typing a portion of its name and hitting Tab then Return. I'm sure it could be done with more efficient sed stuff (comments welcome!), but ... it does the trick for me, and as I'm not writing shell scripts every day...

[robg adds: I tested this, and it does indeed work as described. If you're going to use it regularly, you'll probably want to use cron or some other mechanism to run the generatelinks.sh script on a scheduled basis. If you have applications in multiple places, as I do, you can modify the find command in the first script. Just put additional paths between the -E and the -regex bits on that line:
find -E /Applications/ /Volumes/Apps/Utils -regex ".*\.app$" -print |
Finally, if you change the open.sh script name, make sure you change the reference in generatelinks.sh, too.]
    •    
  • Currently 1.60 / 5
  You rated: 1 / 5 (5 votes cast)
 
[13,348 views]  

Create a Tab-completion-capable GUI app launcher | 21 comments | Create New Account
Click here to return to the 'Create a Tab-completion-capable GUI app launcher' hint
The following comments are owned by whoever posted them. This site is not responsible for what they say.
[nick adds:...
Authored by: nick on Nov 04, '05 06:34:45AM

"For instance, I type iMov[Tab][Enter], and iMovie HD launches; iTu[Tab][Return] for iTunes, etc."

to prevent questions: usually i just hit [Return] as i have remapped the [Enter]-key to [alt]. this little (almost non-)error seems to have slipped in when rob was turning my original hint, our email-conversation and the 2nd version of the script into something thats readable. thanks, rob.]



[ Reply to This | # ]
Create a Tab-completion-capable GUI app launcher
Authored by: wgscott on Nov 04, '05 07:33:56AM
The zsh shell, which comes with OS X, has by default in 10.4, a very good automatic completion system for the command "open -a". Take a look at the file /usr/share/zsh/4.2.3/functions/_open In addition, you can install a faster and somewhat more complete version of the "open -a" completion (and a bunch of other fun stuff) from .

[ Reply to This | # ]
running generatelinks.sh
Authored by: Skurfer on Nov 04, '05 07:40:20AM

Rob mentions using "cron or some other mechanism to run the generatelinks.sh script".

If I understand the way it works, launchd would be a great "other mechanism" because it can be configured to run generatelinks.sh whenever the /Applications directory is changed.

You could create a new plist for launchd in ~/Library/LaunchAgents (since it only needs to be active when you're logged in) and include something like this in the file:

<key>WatchPaths</key>
<array>
  <string>/Applications/</string>
</array>

The only thing I wonder about is whether or not launchd considers you to be "logged in" if you come in remotely via SSH, but I suppose in that case, you're not likely to modify the Applications folder or launch the applications themselves.



[ Reply to This | # ]
running generatelinks.sh
Authored by: GlowingApple on Nov 04, '05 09:20:44AM
So I would need to create the folder LaunchAgents if it doesn't exist and then put a .plist file in that directory with

<key>WatchPaths</key>
<array>
  <string>/Applications/</string>
</array>
as the only text? Do I need to have a special name for this file and is there anything I need to do to have launchd start checking it? (i.e. restart the service or log out/ log in) Thanks!

---
Jayson --When Microsoft asks you, "Where do you want to go today?" tell them "Apple."

[ Reply to This | # ]

running generatelinks.sh
Authored by: nick on Nov 04, '05 10:33:02AM
the file gotta look s/th like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-
1.0.dtd">
<plist version="1.0">
<dict>
        <key>Debug</key>
        <true/>
        <key>Label</key>
        <string>generatelinks</string>
        <key>LowPriorityIO</key>
        <true/>
        <key>Nice</key>
        <integer>10</integer>
        <key>Program</key>
        <string>/Users/nick/Apps/generatelinks.sh</string>
        <key>ProgramArguments</key>
        <array>
                <string>/Users/nick/Apps/generatelinks.sh</string>
        </array>
        <key>ServiceDescription</key>
        <string>generate links for commandline GUI-app launching</string>
        <key>UserName</key>
        <string>nick</string>
        <key>WatchPaths</key>
        <array>
                <string>/Applications/</string>
        </array>
</dict>
</plist>
of course you should adapt the path to the script AND the username. you can make this plist with launchd editor (its on vt, me thinkin) or with the plist-editor (its on your system). the you tell osx to add it to launchd-services:

:~ nick$ launchctl load Library/LaunchAgents/generatelinks.plist 
:~ nick$ launchctl list 
again adapt paths. if you now add s/th to /Applications/ you'll notice the find-process in top. but this way, if you remove apps, they aren't removed from your list. you gotta add s/th like "rm ~/Apps/*.app" to generatelinks.sh for this. then everytime you s/th to /Applications/ the complete list of links is rebuild. this could be a problem with apps writing in their app-bundle though, as the find-command takes its time... regards, n.

[ Reply to This | # ]
running generatelinks.sh
Authored by: GlowingApple on Nov 04, '05 04:28:29PM
Thanks! Works great. I changed the code of the original script a bit to be more conducive to a background task:

#!/bin/sh

cd ~/Apps
find -E /Applications/ -regex ".*.app$" -print |
while read APPS; do
  APPLINK=`echo -n "$APPS" | sed s:".*/":"": | sed s:" ":"_":g`
  if [ ! -e "$APPLINK" ]; then
         ln -s open.sh "$APPLINK"
         logger "Created Application symlink for "$APPLINK
  fi
done
This will verify if the symlink exists first, otherwise when it tries to overwrite it generates an error. It will also output the names of the links it creates to the system log, rather than to stdout, which running within launchd isn't really visible. This doesn't cover the problem with apps removed, but simply doing an rm ~/Apps/*.app periodically should fix this.

---
Jayson --When Microsoft asks you, "Where do you want to go today?" tell them "Apple."

[ Reply to This | # ]

running generatelinks.sh
Authored by: GlowingApple on Nov 04, '05 09:20:44AM
So I would need to create the folder LaunchAgents if it doesn't exist and then put a .plist file in that directory with

<key>WatchPaths</key>
<array>
  <string>/Applications/</string>
</array>
as the only text? Do I need to have a special name for this file and is there anything I need to do to have launchd start checking it? (i.e. restart the service or log out/ log in) Thanks!

---
Jayson --When Microsoft asks you, "Where do you want to go today?" tell them "Apple."

[ Reply to This | # ]

running generatelinks.sh
Authored by: GlowingApple on Nov 04, '05 09:22:55AM

Oops, accidental double-click...sorry for the dup. post.

---
Jayson --When Microsoft asks you, "Where do you want to go today?" tell them "Apple."



[ Reply to This | # ]
Create a Tab-completion-capable GUI app launcher
Authored by: jgrimes on Nov 04, '05 08:01:36AM

Great hint, it's something I've always wanted but never cared enough to do. I especially like the linking of each file to open.sh instead of the clumsier (what I would have done) method of having each file being a shell script that actually opened up the App itself (I'll remember that trick).



[ Reply to This | # ]
you can open files with apps this way, too:
Authored by: nick on Nov 04, '05 08:59:58AM
"Mail.app This.file" will open Mail, create a new message and attache "This.file". should anybody wonder, why i left the *.app extension at the links: this way you can say the difference between GUI-apps and command.

i realized that this is nescessary when i discovered that shell commands aren't case-sensitive [sic!]. at least when they refer to a commandlinetool from the filesystem. not that surprising on a case-insensitive filesystem. aliases from .bashrc are still case-sensitive, btw. (has this been like this before tiger?)

anyway, because of that (and /usr/bin beiing in $PATH before ~/Apps) "Mail" had launched /usr/bin/mail rather than Mail.app.

[ Reply to This | # ]
you can open files with apps this way, too:
Authored by: GlowingApple on Nov 04, '05 09:32:15AM

Tab completion also works case-sensitive. So for me, Mai[Tab], only generates Mail.app, whereas mai[Tab] only generates mail. Interesting how case sensitivity is mixed. I'm anxious for the day when a larger majority of Mac apps support the Mac OS X Extended (journaled, case-sensitive) file system.

---
Jayson --When Microsoft asks you, "Where do you want to go today?" tell them "Apple."



[ Reply to This | # ]
you can open files with apps this way, too:
Authored by: taxi on Nov 04, '05 07:18:29PM

Actually, the filesystem is not case-sensitive. Only case-preserving.

An annoying difference.



[ Reply to This | # ]
you can open files with apps this way, too:
Authored by: cueball on Nov 07, '05 01:32:40AM
"I'm anxious for the day when a larger majority of Mac apps support the Mac OS X Extended (journaled, case-sensitive) file system."
Then:
"Actually, the filesystem is not case-sensitive. Only case-preserving. An annoying difference."
Guess you either didn't read closely enough, or weren't aware, but 10.4 has a case-sensitive version of HFS+J. It's called HFSX. So actually, the filesystem that's referred to by the original poster is case-sensitive.

[ Reply to This | # ]
you can open files with apps this way, too:
Authored by: kholburn on Nov 07, '05 09:40:46PM
If you create the links in the article with all lowercase names then the tab completion will work with lower case.

I have a bash completion fragment that you can put in say ~/.bash_completion or save it to ~/open_completion and source it using the command: . open_completion that handles this in a text file rather than a bunch of links. The mdfind command was from another comment. Use "grep -i" instead of grep to make the completion case insensitive.


#!/bin/sh
#

export appslist=~/.apps.list

_make_app_list () {
  mdfind -onlyin /Applications 
         -onlyin /Developer 
         -onlyin /Applications2 
        "kMDItemContentType == 'com.apple.application-*'" | 
  while read ; do
     echo "${REPLY##*/}"
  done |sort -i > "$appslist"
}

_apple_open ()
{
  local cur prev

  # renew appslist if it's older than a day
  if ! /usr/bin/perl -e '
    my $ARGV = $ARGV[0];
    if (-e $ARGV) { if (time - (stat $ARGV)[9] <= 86400) { exit (0); } }
    exit 1;
  ' "$appslist" ; then
    _make_app_list
  fi

  COMPREPLY=()
  cur=${COMP_WORDS[COMP_CWORD]}
  prev=${COMP_WORDS[COMP_CWORD-1]}

  # do not attempt completion if we're specifying an option
  [[ "$cur" == -* ]] && return 0

  if [ $COMP_CWORD -eq 1 ] || [[ "$prev" == -a ]]; then

    # If we have an appslist
    if [ -s "$appslist" -a -r "$appslist" ]; then
      # Escape dots in paths for grep
      cur=${cur//./.}

      COMPREPLY=( $( grep "^$cur" "$appslist" ) )
    fi
  else
    _filedir
  fi

  return 0
}
complete -F _apple_open open




[ Reply to This | # ]
bash completion
Authored by: kholburn on Nov 08, '05 04:52:31AM
Ummmm the script didn't come out quite right. How do you get backslashes? I've forgotten. Anyway here is a link to text version. The spaces are escaped properly as well.


[ Reply to This | # ]
Create a Tab-completion-capable GUI app launcher
Authored by: vocaro on Nov 04, '05 09:52:13AM
If you're already in the GUI, why not just use QuickSilver? Seems like that would be easier (you don't even have to press Tab in most cases.), and it has more features.

[ Reply to This | # ]
this hint doesn't meant to replace quicksilver...
Authored by: nick on Nov 04, '05 10:06:44AM

...but to complement quicksilver.

although quicksilver is fast, if i'm at the command line within a certain folder -- say my scripts folder -- and wanna mail a file to somebody -- say generatelinks.sh to robg -- its faster to type "Mail[Tab] gen[Tab] [Return]" than do that with quicksilver.

if you're not in the commandline anyway quicksilver is certainly much faster.



[ Reply to This | # ]
and here comes the pissing-contest...
Authored by: nick on Nov 04, '05 10:54:13AM

i got 296 *.app-links in my ~/Apps-directory. and you?

(post the number in the subject)



[ Reply to This | # ]
Create a Tab-completion-capable GUI app launcher
Authored by: 1010011010 on Nov 04, '05 08:40:56PM
Rather than find, use mdfind (on 10.4). Not all applications end in ".app".

$ mdfind "(kMDItemKind = 'Application')"

On my 1GHz powerbook, this command took 0.835 seconds to locate 358 applications.

[ Reply to This | # ]
Create a Tab-completion-capable GUI app launcher
Authored by: wgscott on Nov 05, '05 08:20:48PM
We found that using this in our zsh completion function enables you to skip some of the junk:
mdfind -onlyin /Applications -onlyin /Developer "kMDItemContentType == 'com.apple.application-*'"

[ Reply to This | # ]
Script improvement - args
Authored by: Lutin on Nov 08, '05 07:06:10AM
Thought I would share my improvement of the launcher script. It handles better all the args passed to the command.

#!/bin/sh                                                                                                                                                                             
                                                                                                                                                                                      
APP=`echo -n $0 | sed s:"__":" ":g | sed s:".*/":"": | sed s:".app":"": `                                                                                                             
                                                                                                                                                                                      
if [ "$#" -eq 0 ]; then                                                                                                                                                               
  echo $APP                                                                                                                                                                             
  open -a "$APP"                                                                                                                                                                    
else                                                                                                                                                                                  
  while [ $# -gt 0 ]; do                                                                                                                                                            
      echo $APP $1                                                                                                                                                                  
      open -a "$APP" $1                                                                                                                                                             
      shift;                                                                                                                                                                        
  done                                                                                                                                                                              
fi
I also added -f to the ln command in the generate script, to force the creation if the link already exists.

[ Reply to This | # ]