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 Answers
_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.
-
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 -
-
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 fornospace
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...– LriFeb 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. Feb 13, 2012 at 5:31
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
orKeyboardViewer
this way. *Configure themdfind
call(s) appropriately with the-onlyin
argument.It's kind of slow, due toNow usessystem_profiler SPApplicationsDataType
. You might need to wait a second or two before completion shows up.mdfind
to quickly get the programs. Thanks @LriIt 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 inbash
, 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 completeDVD
toDVD\ Player
.
-
Wouldn't
mdfind 'kMDItemKind==Application'
be faster? Ifcompletion-ignore-case
is set on, grep should probably ignore case as well.– LriFeb 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
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
-
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) Feb 8, 2012 at 23:50 -
1Does not handle program names with spaces (like
DVD Player.app
). Lists directories in/Applications
as well (likeiWork 09
, as separate entriesiWork
and09
), but not the programs contained therein. Feb 9, 2012 at 4:59
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!
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.