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 frequently use the open -a command in Terminal to open applications via ssh. How do I make it auto-complete an application's name?

share|improve this question
    
What shell are you using? –  Daniel Beck Feb 4 '12 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 '12 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 '12 at 16:31
    
@DanielBeck Bash. –  daviesgeek Feb 8 '12 at 23:51
add comment

3 Answers 3

up vote 4 down vote accepted
_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.

share|improve this answer
    
I couldn't get this to work with DVD Player. Any ideas what's wrong? Looks like a Tab instead of a space... –  Daniel Beck Feb 9 '12 at 10:38
    
@DanielBeck It was missing IFS=$'\n'. Anyway, I edited the answer again. –  Lri Feb 9 '12 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. –  Daniel Beck Feb 9 '12 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 '12 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 '12 at 5:31
show 1 more comment

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.

share|improve this answer
    
Wouldn't mdfind 'kMDItemKind==Application' be faster? If completion-ignore-case is set on, grep should probably ignore case as well. –  Lri Feb 9 '12 at 7:27
    
@Lri You're right. Couldn't find a case where these results would have made a difference. –  Daniel Beck Feb 9 '12 at 10:32
add comment

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/
    # bash-completion-devel@lists.alioth.debian.org/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
share|improve this answer
    
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 '12 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. –  Daniel Beck Feb 9 '12 at 4:59
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.