How to enable default file completion in bash

前端 未结 2 1134
说谎
说谎 2021-02-05 18:28

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 wit

相关标签:
2条回答
  • 2021-02-05 19:09

    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
    }
    
    0 讨论(0)
  • 2021-02-05 19:16

    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.

    0 讨论(0)
提交回复
热议问题