16

I frequently use the open -a command in Terminal to open applications via ssh. How do I make it auto-complete an application's name?

4
  • What shell are you using? Feb 4, 2012 at 6:23
  • 2
    I guess a marginally quicker way would be to type out the full path (just go with me for a while here!!), e.g. open -a /Applications/Textedit.app foo.txt (I assume thats what you're trying to do). If you press Tab after the /A of /Applications and then Tab again after the /Te of /Textedit.app then that should autocomplete both parts for you as you go. Not ideal, I admit, but maybe a but better. This was using Bash.
    – binarybob
    Feb 4, 2012 at 7:55
  • You could also try following through the instructions at the end of this post: brettterpstra.com/grabbing-a-mac-apps-icon-advanced-bash-usage-2
    – Lri
    Feb 4, 2012 at 16:31
  • @DanielBeck Bash.
    – daviesgeek
    Feb 8, 2012 at 23:51

4 Answers 4

9
_complete_open() {
        COMPREPLY=()
        local cur="${COMP_WORDS[$COMP_CWORD]}"
        local prev="${COMP_WORDS[COMP_CWORD-1]}"
        [[ "$cur" == -* || "$prev" != '-a' ]] && return
        apps="$(mdfind kMDItemKind==Application -onlyin /Applications -onlyin ~/Applications -onlyin /Developer -onlyin ~/Developer | grep -v '/.*/.*/.*/.*/' | sed -E 's|.*/||g;s|\.app$||g' | uniq)"$'Finder\nArchive Utility\nCharacterPalette\nKeyboardViewer'
        local IFS=$'\n'
        if [[ "${cur:0:1}" = '"' || "${cur:0:1}" = "'" ]]; then
            quote="${cur:0:1}"
            cur="${cur:1}"
        fi
        local found="$(grep -i "^$cur" <<< "$apps")"
        if [[ "$quote" == '"' ]]; then
            found="$(sed "s|^|\"|g;s|$|\"|g" <<< "$found")"
        elif [[ "$quote" == "'" ]]; then
            found="$(sed "s|^|'|g;s|$|'|g" <<< "$found")"
        else
            found="$(sed 's| |\\ |g' <<< "$found")"
        fi
        COMPREPLY=($found)
}

complete -o default -F _complete_open open

Third version, which should now both be case insensitive and work within quotes.

6
  • I couldn't get this to work with DVD Player. Any ideas what's wrong? Looks like a Tab instead of a space... Feb 9, 2012 at 10:38
  • @DanielBeck It was missing IFS=$'\n'. Anyway, I edited the answer again.
    – Lri
    Feb 9, 2012 at 13:13
  • Looks good. A late +1 for the mdfind idea. Nice work on the optimization and fixes. CoreServices are probably personal preference though. What's the reason for nospace though? If there's only 1 program, I want to continue right away with the filen to open. Just personal preference? Any idea about the remaining quoting issue? AFAICT, that's the only thing left for a proper solution. Feb 9, 2012 at 13:54
  • @DanielBeck I always put the -a flag at the end, so I guess -o nospace is just a personal preference. Maybe we should ping Brett Terpstra or something to finish this nerdfest...
    – Lri
    Feb 9, 2012 at 15:38
  • Thank you so much!! @DanielBeck you too. I chose Lri's version because of the speed. Unfortunately, yours was a bit slow. Thanks so much both of you! You've helped me a lot and made my Terminal speed increase.
    – daviesgeek
    Feb 13, 2012 at 5:31
7

Add the following to your .bash_profile or .bashrc and launch a new session:

function _complete_open {
    cur=$2
    COMPREPLY=( );

    [[ "$COMP_WORDS" = "open" ]] || return
    [[ "${COMP_WORDS[ $(( $COMP_CWORD - 1 )) ]}" = "-a" ]] || return

    OLDIFS="$IFS"
    IFS=$'\n'
    local _part="${COMP_WORDS[$COMP_CWORD]}"

    if [[ "${_part:0:1}" = '"' || "${_part:0:1}" = "'" ]] ; then
        COMPREPLY=( $( compgen -W "$( mdfind kMDItemKind==Application | sed -e 's|.*/||g' -e 's|.app$||' | sort -u )" -- $cur ) )
    else
        COMPREPLY=( $( compgen -W "$( mdfind kMDItemKind==Application | sed -e 's|.*/||g' -e 's|.app$||' -e 's| |\\\\ |g' | sort -u )" -- $cur ) )
    fi
    IFS="$OLDIFS"
}

complete -o default -F _complete_open open

No need to install anything. This works with bash out of the box.


It will only autocomplete program names if the previous option is -a and otherwise show default behavior, e.g. return a list of all files in the current directory or complete the current path prefix.

The results are generated from system_profiler SPApplicationsDataType, which is the easiest way to get all applications that can be launched this way on your system like that. The list is processed to only return the program names, which can contain spaces and can be different from the bundle names (even when ignoring .app suffix)

Usage: Type open -a, followed by a space, followed by pressing Tab or Esc (twice on my system, not sure if it's everywhere).

Example showing all the helper applications for my scanner:

$ open -a Scan
Scan to E-mail          Scan to Excel           Scan to Folder          Scan to Print           Scan to Searchable PDF  Scan to Word            ScanSnap Manager

Drawbacks and problems of this solution:

  • There are tons of programs on your system you might not be aware of, like everything in /System/Library/CoreServices. You might not want to list all of them. OTOH, it's really easy to see and launch e.g. CharacterPalette or KeyboardViewer this way. *Configure the mdfind call(s) appropriately with the -onlyin argument.

  • It's kind of slow, due to system_profiler SPApplicationsDataType. You might need to wait a second or two before completion shows up. Now uses mdfind to quickly get the programs. Thanks @Lri

  • It can handle spaces in application names, and quotation enclosed program names, but it's rather hacky. It requires the quote to be the first character: While Scan" to "P is valid in bash, this program will not detect it. Completion does not work after an escaped space either (e.g. Scan\ to), use quotation marks in such cases ("Scan to). Support for escaped spaces is only good to complete DVD to DVD\ Player.

2
  • Wouldn't mdfind 'kMDItemKind==Application' be faster? If completion-ignore-case is set on, grep should probably ignore case as well.
    – Lri
    Feb 9, 2012 at 7:27
  • @Lri You're right. Couldn't find a case where these results would have made a difference. Feb 9, 2012 at 10:32
3

Programmeable autocompletion to the rescue! Needed a lot of copying from the Bash Completion Homepage though, which is worth installing anyway for a lot of auto-completion magic. If you do, you will only need the last function (_open) and the initialising command from below.

Add the following to .bashrc:

# taken from http://bash-completion.alioth.debian.org/

_compopt_o_filenames()
{
    # We test for compopt availability first because directly invoking it on
    # bash < 4 at this point may cause terminal echo to be turned off for some
    # reason, see https://bugzilla.redhat.com/653669 for more info.
    type compopt &>/dev/null && compopt -o filenames 2>/dev/null || \
        compgen -f /non-existing-dir/ >/dev/null
}

_tilde() {
    local result=0
    # Does $1 start with tilde (~) and doesn't contain slash (/)?
    if [[ ${1:0:1} == "~" && $1 == ${1//\/} ]]; then
        _compopt_o_filenames
        # Try generate username completions
        COMPREPLY=( $( compgen -P '~' -u "${1#\~}" ) )
        result=${#COMPREPLY[@]}
    fi
    return $result
}

_quote_readline_by_ref()
{
    if [[ ${1:0:1} == "'" ]]; then
        if [[ ${BASH_VERSINFO[0]} -ge 4 ]]; then
            # Leave out first character
            printf -v $2 %s "${1:1}"
        else
            # Quote word, leaving out first character
            printf -v $2 %q "${1:1}"
            # Double-quote word (bash-3)
            printf -v $2 %q ${!2}
        fi
    elif [[ ${BASH_VERSINFO[0]} -le 3 && ${1:0:1} == '"' ]]; then
        printf -v $2 %q "${1:1}"
    else
        printf -v $2 %q "$1"
    fi

    # If result becomes quoted like this: $'string', re-evaluate in order to
    # drop the additional quoting.  See also: http://www.mail-archive.com/
    # [email protected]/msg01942.html
    [[ ${!2:0:1} == '$' ]] && eval $2=${!2}
} # _quote_readline_by_ref()

_filedir()
{
    local i IFS=$'\n' xspec

    _tilde "$cur" || return 0

    local -a toks
    local quoted tmp

    _quote_readline_by_ref "$cur" quoted
    toks=( ${toks[@]-} $(
        compgen -d -- "$quoted" | {
            while read -r tmp; do
                printf '%s\n' $tmp
            done
        }
    ))

    if [[ "$1" != -d ]]; then
        # Munge xspec to contain uppercase version too
        [[ ${BASH_VERSINFO[0]} -ge 4 ]] && \
            xspec=${1:+"!*.@($1|${1^^})"} || \
            xspec=${1:+"!*.@($1|$(printf %s $1 | tr '[:lower:]' '[:upper:]'))"}
        toks=( ${toks[@]-} $( compgen -f -X "$xspec" -- $quoted) )
    fi
    [ ${#toks[@]} -ne 0 ] && _compopt_o_filenames

    COMPREPLY=( "${COMPREPLY[@]}" "${toks[@]}" )
} # _filedir()

# only the following is needed if bash-autocompletion is already installed
_open ()
{
    local cur;

    cur=$2;

    COMPREPLY=();
    if [ $COMP_CWORD -eq 2 ]; then
        COMPREPLY=($(compgen -W "$(/bin/ls /Applications)" -- $cur ));
        return 0
    fi

    _filedir
}

complete -F _open open
2
  • So I followed the install instructions and added the code to .bashrc. Now how do I get it to autocomplete? What do I push to autocomplete? (Sorry, I can't read the code)
    – daviesgeek
    Feb 8, 2012 at 23:50
  • 1
    Does not handle program names with spaces (like DVD Player.app). Lists directories in /Applications as well (like iWork 09, as separate entries iWork and 09), but not the programs contained therein. Feb 9, 2012 at 4:59
1

This comes enabled by default with the Zsh shell, as long as you have completion itself enabled, of course. Zsh is included in OS X.

All you have to do is put autoload -Uz compinit; compinit in your ~/.zshrc, or enable completion through the first run configuration menu.

Zsh is actually mostly a superset of Bash, so you shouldn't have to learn anything new. Even your scripts will probably work!

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .