CDPATH and bash_completion in ubuntu 11.04: an anoying combination

Computing 2011. 5. 11. 14:33

Since bash version 3.2, there has been a handy feature called 'tab completion'.

But with ubuntu distribution, the cool feature becomes somewhat like pain in the ass. Why? Look at following example:

shawn.ygdrasil:~/work/bash_completion$ mkdir foo
shawn.ygdrasil:~/work/bash_completion$ mkdir bar
shawn.ygdrasil:~/work/bash_completion$ ls
bar/  foo/
shawn.ygdrasil:~/work/bash_completion$ export CDPATH=~/work/bash_completion
shawn.ygdrasil:~/work/bash_completion$ cd foo
/home/shawn/work/bash_completion/foo
shawn.ygdrasil:~/work/bash_completion/foo$ cd bar
/home/shawn/work/bash_completion/bar

OK, so far, so good.

shawn.ygdrasil:~/work/bash_completion/bar$ cd f

After typing in "cd f" and then you press TAB key, suddenly "f" becomes "foo/". Even when  there is no directory in the bar directory:

shawn.ygdrasil:~/work/bash_completion/bar$ cd foo/

Someone would say that this is nicer -- obviously ubuntu guys think so. But I absolutely do not think so. Since I don't want to get caught arguing which one is better, let's cut to the chase.

How to make bash's cool tab completion feature not to search matches in CDPATH?

Bash reads (at least with ubuntu distributions) /etc/profile when it is invoked. And the /etc/profile sources /etc/profile.d/*.sh. And there is this file: /etc/profile.d/bash_completion.sh. In this file, again sources /etc/bash_completion where there exists a function which searches for directory names when TAB key is typed in. The name of the function is "_cd()".

If you don't like bash completion to search for matches in CDPATH, you simply comment out some of the _cd() function in the /etc/bash_completion like:

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

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

    _compopt_o_filenames

    # 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//:/$'\n'}; 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
}

Has the pain in the ass gone?

Good luck.


: