Bash – Prevent customized bash autocomplete from affecting io redirection (< and >)

autocompletebashio-redirection

I've used complete -W 'firstdatabase databasetwo etc' mysql to save a list of databases, so that I can autocomplete them using the mysql command.

However, in many cases I would type mysql db < /tmp/script.sql. How can I get bash to revert to traditional autocomplete (filenames) after seeing the first > or <?


Update: I pulled up a stock Ubuntu install, which comes with the bash-autocomplete package pre-installed. While simply installing that same package on our server is not an option for me, I can still learn from its implementation by examining the output of complete -p and declare. (alias -p has some cool stuff as well)

On our server, I used complete -d cd. I observe that it suffers from the same issue in that after < or >, the complete -d functionality persists. However, bash-autocomplete has a more elaborate approach which solves this issue. Learning how they solved it for cd (and a multitude of other commands) may show how to solve the same issue I'm having for mysql.

So, here is their implementation for cd: They use complete -o nospace -F _cd cd, which basically calls function _cd for the autocomplete query. Here is the function they use:

_cd ()
{ 
    local cur prev words cword;
    _init_completion || return;
    local IFS='
' i j k;
    compopt -o filenames;
    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);
    for i in ${CDPATH//:/'
'};
    do
        k="${#COMPREPLY[@]}";
        for j in $( compgen -d $i/$cur );
        do
            if [[ ( -n $mark_symdirs && -h $j || -n $mark_dirs && ! -h $j ) && ! -d ${j#$i/} ]]; then
                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
}

complete -o nospace -F _cd cd

Running the above code on a recent version of bash gives the desired results for cd. Now how can this be converted for mysql? (or anything I'd normally use complete -W for?)

Best Answer

The magic is in the _init_completion function which is defined in the bash_completion file of the bash-completion package.

There's no easy way out. If you want sensible fancy completion in bash, install the bash-completion package. Then define a function:

. /etc/bash_completion
_my_mysql () {
  local cur prev words cword
  _init_completion || return
  COMPREPLY=($(compgen -W 'firstdatabase databasetwo etc' "$cur"))
}
complete -F _my_mysql mysql