Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I replace a command line argument with tab completion?

I'm wondering if this is possible in Bash, but I'd like to use tab completion to completely replace the current argument that's being expanded. I'll give an example: I'd like to have a function that moves up an arbitrary number of levels in the tree, so I can call up 2 And that would cd me 2 directories up. However, I would like to make it so that if at the number 2, I press tab, it will expand that number to being the path (either relative or absolute, either is fine). I have this almost working using the complete builtin except it will only append the text, so it will be something like up 2/Volumes/Dev/

Is it possible to replace the completed symbol?

Thanks in advance :)

Update:

So a big thanks to chepner, because actually checking my code revealed where my bug was. I was comparing against the wrong var, and the debug code I had was causing the value to not replace.

For anyone interested, here's the code (and there could be a much better way to accomplish this):

# Move up N levels of the directory tree
# Or by typing in some dir in the PWD
# eg. Assuming your PWD is "/Volumes/Users/natecavanaugh/Documents/stuff"
#     `up 2` moves up 2 directories to "/Volumes/Users/natecavanaugh"
#     `up 2/` and pressing tab will autocomplete the dirs in "/Volumes/Users/natecavanaugh"
#     `up Users` navigate to "/Volumes/Users"
#     `up us` and pressing tab will autocomplete to "/Volumes/Users"
function up {
    dir="../"
    if [ -n "$1" ]; then
        if [[ $1 =~ ^[0-9]+$ ]]; then
            strpath=$( printf "%${1}s" );
            dir=" ${strpath// /$dir}"
        else
            dir=${PWD%/$1/*}/$1
        fi
    fi

    cd $dir
}

function _get_up {
    local cur
    local dir
    local results
    COMPREPLY=()
    #Variable to hold the current word
    cur="${COMP_WORDS[COMP_CWORD]}"

    local lower_cur=`echo ${cur##*/} | tr [:upper:] [:lower:]`

    # Is the arg a number or number followed by a slash
    if [[ $cur =~ ^[0-9]+/? ]]; then
        dir="../"
        strpath=$( printf "%${cur%%/*}s" );
        dir=" ${strpath// /$dir}"

        # Is the arg just a number?
        if [[ $cur =~ ^[0-9]+$ ]]; then
            COMPREPLY=($(compgen -W "${dir}"))
        else
            if [[ $cur =~ /.*$ ]]; then
                cur="${cur##*/}"
            fi

            results=$(for t in `cd $dir && ls -d */`; do if [[ `echo $t | tr [:upper:] [:lower:]` == "$lower_cur"* ]]; then echo "${t}"; fi done)

            COMPREPLY=($(compgen -P "$dir" -W "${results}"))
        fi
    else
        # Is the arg a word that we can look for in the PWD
        results=$(for t in `echo $PWD | tr "/" "\n"`; do if [[ `echo $t | tr [:upper:] [:lower:]` == "$lower_cur"* ]]; then echo "${t}"; fi; done)

        COMPREPLY=($(compgen -W "${results}"))
    fi  
}

#Assign the auto-completion function _get for our command get.
complete -F _get_up up
like image 910
Nate Cavanaugh Avatar asked May 11 '12 12:05

Nate Cavanaugh


People also ask

Does bash have tab completion?

Bourne shell and csh do not, but ksh, bash, tcsh, and zsh all have tab completion to varying degrees. The basic principle in all of these shells is the same; you type the start of the word, hit the <TAB> key twice, and the list of possible commands or files is displayed.

How do you completion a tab in Linux?

Tab Completion on Linux For example, let's say you want to run the firefox command. You can just type fir or fire into the terminal and press Tab — if your system doesn't have any other commands that begin with those letters, Bash will automatically fill in firefox and you can press Enter to run the command.

What is tab completion in PowerShell?

PowerShell provides completions on input to provide hints, enable discovery, and speed up input entry. Command names, parameter names, argument values and file paths can all be completed by pressing the Tab key. The Tab key is the default key binding on Windows.


1 Answers

It is possible to completely replace the current word with a single new word. With my bash 4.2.29, I can do this:

_xxx() { COMPREPLY=( foo ); }
complete -F _xxx x
x bar # pressing tab turns this into x foo

You encounter problems, however, if there is more than one possible completion, and you want to get partial completion of the common prefix. Then my experiments indicate that bash will try to match the available completions to the prefix you entered.

So in general you should probably only replace the current argument with something completely different if that something is uniquely defined. Otherwise, you should generate completions which match the current prefix, to have the user select from those. In your case you could replace the COMPREPLY=($(compgen -P "$dir" -W "${results}")) with something along these lines:

local IFS=$'\n'
COMPREPLY=( $(find "${dir}" -maxdepth 1 -type d -iname "${cur#*/}*" -printf "%P\n") )
if [[ ${#COMPREPLY[@]} -eq 1 ]]; then
    COMPREPLY=( "${dir}${COMPREPLY[0]}" )
fi

However, in this specific case it might be better to only replace the prefix digit by the appropriate path, and leave everything else to the default bash completion:

_up_prefix() {
    local dir cur="${COMP_WORDS[COMP_CWORD]}"
    COMPREPLY=()

    if [[ ${cur} =~ ^[0-9]+/? ]]; then
        # Starting with a number, possibly followed by a slash
        dir=$( printf "%${cur%%/*}s" );
        dir="${dir// /../}"
        if [[ ${cur} == */* ]]; then
            dir="${dir}${cur#*/}"
        fi
        COMPREPLY=( "${dir}" "${dir}." ) # hack to suppress trailing space
    elif [[ ${cur} != */* ]]; then
        # Not a digit, and no slash either, so search parent directories
        COMPREPLY=( $(IFS='/'; compgen -W "${PWD}" "${cur}") )
        if [[ ${#COMPREPLY[@]} -eq 1 ]]; then
            dir="${PWD%${COMPREPLY[0]}/*}${COMPREPLY[0]}/"
            COMPREPLY=( "${dir}" "${dir}." ) # hack as above
        fi
    fi
}

complete -F _up_prefix -o dirnames up

The code becomes a lot easier to read and maintain, plus more efficient to boot. The only drawback is that in some cases you'd have to press tab one more time than you used to: once to substitute the prefix, and twice more to actually see the list of possible completions. Your choice whether that's acceptable.

One more thing: the completion will turn the argument into a regular path, but your up function as it is does not accept those. So perhaps you should start that function with a [[ -d $1 ]] check, and simply cd to that directory if it exists. Otherwise your completion will generate arguments which are unacceptable to the called function.

like image 178
MvG Avatar answered Nov 18 '22 22:11

MvG