[Bash-completion-devel] Alias handling

Mario Schwalbe m3o.s6e at googlemail.com
Wed Jul 30 08:54:15 UTC 2014


Hi,

I'm using the great bash-completion package for quite a while now,
but I find it lacks a good support of aliases. (Yes, I know they are
deprecated in favour of shell functions, but there are many people
out there which use them a lot.)

Therefore, I'd like to propose the attached code to be included in a
future release. It basically rebinds the completion of the aliased
command once completion of an alias is attempted.

However, there are some caveats:
(a) An aliased command must not contain whitespace in its name, as
    it is used to split words.
(b) Does not support semantically broken aliases, like:
    alias foo='SomeCommand --user'
    because the rebound completion handler of SomeCommand cannot see
    that the previous word would be --user to propose user names.
(c) Currently, handles sudo and the _longopt completion function specially.
    There might be other commands/functions where that could be necessary.

If you are interested/have further questions/suggestions, drop me an email.

ciao,
Mario
-------------- next part --------------
#
# vim: set tabstop=4 shiftwidth=4 noexpandtab filetype=sh:
#
# File:
#   GenericAliases.bash
#
# Description:
#   Bash-completion helpers to handle aliases.
#
# Author(s):
#   (c) 2012-2014 Mario Schwalbe (m3o.s6e at googlemail.com)
#

#
# Get aliased command for alias ($1).
# Returns: 0 on success, 1 otherwise.
#
__get_aliased_command()
{
	local aliasFor=$(alias "$1" 2> /dev/null)
	[ -z "$aliasFor" ] && return 1

	# strip alias specification stuff
	aliasFor=${aliasFor#*\'}
	aliasFor=${aliasFor%\'}

	# remove leading and trailing spaces
	aliasFor=${aliasFor##+( )}
	aliasFor=${aliasFor%%+( )}

	# strip first word of command unless like `alias so=sudo -E'
	if [[ "$aliasFor" == "sudo "* ]] ; then
		local w
		for w in ${aliasFor#* } ; do
			if [[ "$w" != -* ]] ; then
				aliasFor=$w
				break
			fi
		done
	fi

	# get first word of command
	aliasFor=${aliasFor%% *}

	# stip path to avoid endless recursion due to `alias ps=/bin/ps fax'
	aliasFor=${aliasFor##*/}

	echo $aliasFor
}

#
# Rebind previously existing completion of a command ($2) to another command ($1).
# Returns: 0 on success, 1 otherwise.
#
__rebind_completion()
{
	if [ $# -lt 2 ] ; then
		echo "__rebind_completion: Missing arument(s): $@" > /dev/stderr
		return 1
	fi

	local cmd=$1 aliasFor=$2

	# get completion
	local oldComp=$(complete -p "$aliasFor" 2> /dev/null)

	if [ -n "$oldComp" ] ; then
		# replace trailing command part
		local newComp=${oldComp/% $aliasFor/ $cmd}

		if [ "$newComp" != "$oldComp" ] ; then
			if [[ "$newComp" == *"-F _longopt "* ]] ; then
				eval "
					__${cmd}_longopt()
					{
						_longopt $aliasFor \${@:2}
					}
				"
				newComp=${newComp/_longopt/__${cmd}_longopt}
			fi

			$newComp > /dev/null 2>&1
			return $?
		fi
	fi

	return 1
}

#
# Lookup and rebind previously existing completion of a command ($2) to another command ($1).
# Returns: 0 on success, 1 otherwise.
#
__lookup_completion()
{
	if [ $# -lt 2 ] ; then
		echo "__lookup_completion: Missing arument(s): $@" > /dev/stderr
		return 1
	fi

	local cmd=$1 aliasFor=$2

	if complete -p "$aliasFor" > /dev/null 2>&1 ; then
		# have completion: rebind
		__rebind_completion $cmd "$aliasFor"
		return $?
	elif [ "$aliasFor" != "${aliasFor##*/}" ] ; then
		# aliased command contained a path: retry without
		__lookup_completion $cmd "${aliasFor##*/}"
		return $?
	else
		# did not find anything: try to expand one more time
		local nextAliasFor=$(__get_aliased_command "$aliasFor")

		# break recursion in case of `alias ls=ls --color=auto' or end of list
		if [ -n "$nextAliasFor" -a "$nextAliasFor" != "$aliasFor" ] ; then
			__lookup_completion $cmd "$nextAliasFor"
			return $?
		else
			_completion_loader "$aliasFor"
			__rebind_completion $cmd "$aliasFor"
			return $?
		fi
	fi

	echo "__lookup_completion: FIXME: unexpectedly reached end" > /dev/stderr
	return 1
}

#
# Default completion handler to rebind aliases on demand.
# Parameters:
#     1. command/alias ... command/alias to resolve completion for
#     2. current word  ... (see BASH completions)
#     3. previous word ... (see BASH completions)
# Returns:
#     0 on success, 124 retry completion, 1 otherwise.
#
__lookup_completion_handler()
{
	local cmd=$1 aliasFor=$(__get_aliased_command "$1")

	if [ -n "$aliasFor" ] ; then
		# try to resolve chains of aliases and rebind completion
		__lookup_completion $cmd "$aliasFor" && return 124
		return $?
	else
		# not an alias: call default implementation
		_completion_loader $cmd
		return $?
	fi
}

# overwrite default completion handler to also lookup aliases
complete -F __lookup_completion_handler -D

# ***** end of source *****



More information about the Bash-completion-devel mailing list