Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to enable default file completion in bash

Tags:

bash

Say I have a command named "foo" that takes one argument (either "encrypt" or "decrypt") and then a list of files. I want to write a bash completion script that helps with the first argument and then enables standard file completion for the others. The closest I've been able to come is this:

complete -F _foo foo

function _foo()
{
    local cmds cur
    if [ $COMP_CWORD -eq 1 ]
    then
        cur="${COMP_WORDS[1]}"
        cmds=("encrypt decrypt")
        COMPREPLY=($(compgen -W "${cmds}" -- ${cur}) )
    else
        COMPREPLY=($(compgen -f ${COMP_WORDS[${COMP_CWORD}]} ) )
    fi
}

This does the first argument correctly and will chose a filename from the current directory for the subsequent ones. But say the current directory contains a subdirectory bar that contains a file baz.txt. After typing ba-TAB-TAB, completion results in "bar " (space after the "r") and is ready to choose the next argument. What I want is the standard bash completion, where the result is "bar/" (no space after the slash), ready to choose a file in the subdirectory. Is there any way to get that?

like image 537
Mike Schilling Avatar asked Jan 25 '13 00:01

Mike Schilling


People also ask

How do you add a completion Tab in bash?

Bash completion is a bash function that allows you to auto complete commands or arguments by typing partially commands or arguments, then pressing the [Tab] key. This will help you when writing the bash command in terminal.

Does bash have autocomplete?

The programmable completion feature in Bash permits typing a partial command, then pressing the [Tab] key to auto-complete the command sequence. [1] If multiple completions are possible, then [Tab] lists them all. Let's see how it works. Tab completion also works for variables and path names.

How can I tell if I have bash completion?

Install bash-completion Depending on your package manager, you have to manually source this file in your ~/. bashrc file. Reload your shell and verify that bash-completion is correctly installed by typing type _init_completion .

How do you auto complete in Linux?

Command auto-completion is available when using the CLI shell. You can set up CLI command auto-completion in your operating system using the kollacli command command. This command generates a script you can save to the operating system. Copy and paste the output to a file, for example a file named /etc/bash_completion.


2 Answers

I know this is a little late, but I have found a solution here: https://stackoverflow.com/a/19062943/108105

Basically, you use complete -o bashdefault -o default, and when you want to revert to the default bash completion you set COMPREPLY=(). Here's an example:

complete -o bashdefault -o default -F _foo foo
_foo() {
    local cur=${COMP_WORDS[COMP_CWORD]}
    if (( $COMP_CWORD == 1 )); then
        COMPREPLY=( $(compgen -W 'encrypt decrypt' -- "$cur") )
    else
        COMPREPLY=()
    fi
}
like image 98
Tom Dalling Avatar answered Sep 28 '22 03:09

Tom Dalling


The bash documentation can be a little enigmatic. The simplest solution is to alter the completion function binding:

complete -o filenames -F _foo foo

This means the function returns filenames (including directories), and special handling of the results is enabled.

(IMHO the documentation doesn't make it clear that this effectively post-processes COMPREPLY[] as set by your completion function; and that some of the -o options, that one included, when applied to compgen appear to have no effect.)

You can get closer to normal bash behaviour by using:

 complete -o filenames -o bashdefault -F _foo foo

that gets you "~" completion back.

There are two problems with the above however:

  • if you have a directory named "encrypt" or "decrypt" then the expansion of your keywords will grow a trailing "/"
  • $VARIABLE expansion won't work, $ will become \$ to better match a filename with a $. Similarly @host expansion won't work.

The only way that I have found to deal with this is to process the compgen output, and not rely on the "filenames" post-processing:

_foo()
{
    local cmds cur ff
    if (($COMP_CWORD == 1))
    then
        cur="${COMP_WORDS[1]}"
        cmds="encrypt decrypt"
        COMPREPLY=($(compgen -W "$cmds" -- "$cur"))
        COMPREPLY=( "${COMPREPLY[@]/%/ }" )   # add trailing space to each
    else
        # get all matching files and directories
        COMPREPLY=($(compgen -f  -- "${COMP_WORDS[$COMP_CWORD]}"))

        for ((ff=0; ff<${#COMPREPLY[@]}; ff++)); do
            [[ -d ${COMPREPLY[$ff]} ]] && COMPREPLY[$ff]+='/'
            [[ -f ${COMPREPLY[$ff]} ]] && COMPREPLY[$ff]+=' '
        done
    fi
}

complete -o bashdefault -o default -o nospace -F _foo foo

(I also removed the superfluous array for cmd in the above, and made compgen more robust and handle spaces or leading dashes in filenames.)

The downsides now are that when you get the intermediate completion list (i.e. when you hit tab twice to show multiple matches) you won't see a trailing / on directories, and since nospace is enabled the ~ $ @ expansions won't grow a space to cause them to be accepted.

In short, I do not believe you can trivially mix-and-match your own completion and the full bash completion behaviour.

like image 20
mr.spuratic Avatar answered Sep 28 '22 01:09

mr.spuratic