Properly handling spaces and quotes in bash completion

后端 未结 5 2012
悲&欢浪女
悲&欢浪女 2020-12-05 06:54

What is the correct/best way of handling spaces and quotes in bash completion?

Here’s a simple example. I have a command called words (e.g., a dictionar

5条回答
  •  盖世英雄少女心
    2020-12-05 07:32

    This not too elegant postprocessing solution seems to work for me (GNU bash, version 3.1.17(6)-release (i686-pc-cygwin)). (Unless I didn't test some border case as usual :))

    Don't need to eval things, there are only 2 kinds of quotes.

    Since compgen doesn't want to escape spaces for us, we will escape them ourselves (only if word didn't start with a quote). This has a side effect of full list (on double tab) having escaped values as well. Not sure if that's good or not, since ls doesn't do it...

    EDIT: Fixed to handle single and double qoutes inside the words. Essentially we have to pass 3 unescapings :). First for grep, second for compgen, and last for words command itself when autocompletion is done.

    _find_words()
    {
        search=$(eval echo "$cur" 2>/dev/null || eval echo "$cur'" 2>/dev/null || eval echo "$cur\"" 2>/dev/null || "")
        grep -- "^$search" words.dat | sed -e "{" -e 's#\\#\\\\#g' -e "s#'#\\\'#g" -e 's#"#\\\"#g' -e "}"
    }
    
    _words_complete()
    {
        local IFS=$'\n'
    
        COMPREPLY=()
        local cur="${COMP_WORDS[COMP_CWORD]}"
    
        COMPREPLY=( $( compgen -W "$(_find_words)" -- "$cur" ) )
    
        local escaped_single_qoute="'\''"
        local i=0
        for entry in ${COMPREPLY[*]}
        do
            if [[ "${cur:0:1}" == "'" ]] 
            then
                # started with single quote, escaping only other single quotes
                # [']bla'bla"bla\bla bla --> [']bla'\''bla"bla\bla bla
                COMPREPLY[$i]="${entry//\'/${escaped_single_qoute}}" 
            elif [[ "${cur:0:1}" == "\"" ]] 
            then
                # started with double quote, escaping all double quotes and all backslashes
                # ["]bla'bla"bla\bla bla --> ["]bla'bla\"bla\\bla bla
                entry="${entry//\\/\\\\}" 
                COMPREPLY[$i]="${entry//\"/\\\"}" 
            else 
                # no quotes in front, escaping _everything_
                # [ ]bla'bla"bla\bla bla --> [ ]bla\'bla\"bla\\bla\ bla
                entry="${entry//\\/\\\\}" 
                entry="${entry//\'/\'}" 
                entry="${entry//\"/\\\"}" 
                COMPREPLY[$i]="${entry// /\\ }"
            fi
            (( i++ ))
        done
    }
    

提交回复
热议问题