bash で cd 時に $CDPATH を補完候補に

したい。bash_completion をインストールすればいいのだが、丸ごとソースするには重過ぎるので必要な部分だけを ~/.bashrc に書きたいと思った。
以下を追加するとできた。

# Turn on extended globbing and programmable completion
shopt -s extglob progcomp 

# This function checks whether a given readline variable
# is `on'.
#
_rl_enabled() 
{
    [[ "$( bind -v )" = *$1+([[:space:]])on* ]]
}

# This function performs file and directory completion. It's better than
# simply using 'compgen -f', because it honours spaces in filenames.
# If passed -d, it completes only on directories. If passed anything else,
# it's assumed to be a file glob to complete on.
#
_filedir()
{
	local IFS=$'\t\n' xspec #glob

	_expand || return 0

	#glob=$(set +o|grep noglob) # save glob setting.
	#set -f		 # disable pathname expansion (globbing)

	if [ "${1:-}" = -d ]; then
		COMPREPLY=( ${COMPREPLY[@]:-} $( compgen -d -- $cur ) )
		#eval "$glob"    # restore glob setting.
		return 0
	fi

	xspec=${1:+"!*.$1"}	# set only if glob passed in as $1
	COMPREPLY=( ${COMPREPLY[@]:-} $( compgen -f -X "$xspec" -- "$cur" ) \
		    $( compgen -d -- "$cur" ) )
	#eval "$glob"    # restore glob setting.
}

# This function expands tildes in pathnames
#
_expand()
{
	[ "$cur" != "${cur%\\}" ] && cur="$cur\\"

	# expand ~username type directory specifications
	if [[ "$cur" == \~*/* ]]; then
		eval cur=$cur
		
	elif [[ "$cur" == \~* ]]; then
		cur=${cur#\~}
		COMPREPLY=( $( compgen -P '~' -u $cur ) )
		return ${#COMPREPLY[@]}
	fi
}

# This meta-cd function observes the CDPATH variable, so that cd additionally
# completes on directories under those specified in CDPATH.
#
_cd()
{
	local IFS=$'\t\n' cur=${COMP_WORDS[COMP_CWORD]} i j k

	# try to allow variable completion
	if [[ "$cur" == ?(\\)\$* ]]; then
		COMPREPLY=( $( compgen -v -P '$' -- "${cur#?(\\)$}" ) )
		return 0
	fi

	# Use standard dir completion if no CDPATH or parameter starts with /,
	# ./ or ../
	if [ -z "${CDPATH:-}" ] || [[ "$cur" == ?(.)?(.)/* ]]; then
		_filedir -d
		return 0
	fi

	local -r mark_dirs=$(_rl_enabled mark-directories && echo y)
	local -r mark_symdirs=$(_rl_enabled mark-symlinked-directories && echo y)

	# we have a CDPATH, so loop on its contents
	for i in ${CDPATH//:/$'\t'}; do
		# create an array of matched subdirs
		k=${#COMPREPLY[@]}
		for j in $( compgen -d $i/$cur ); do
			if [[ ( $mark_symdirs && -h $j || $mark_dirs && ! -h $j ) && ! -d ${j#$i/} ]]; then
				j="${j}/"
			fi
			COMPREPLY[k++]=${j#$i/}
		done
	done

	_filedir -d

	if [[ ${#COMPREPLY[@]} -eq 1 ]]; then
	    i=${COMPREPLY[0]}
	    if [ "$i" == "$cur" ] && [[ $i != "*/" ]]; then
		COMPREPLY[0]="${i}/"
	    fi
	fi
	    
	return 0
}
nospace="-o nospace"
if shopt -q cdable_vars; then
    complete -v -F _cd $nospace $filenames cd
else
    complete -F _cd $nospace $filenames cd
fi