正确处理空白和报价完成bash [英] Properly handling spaces and quotes in bash completion

查看:140
本文介绍了正确处理空白和报价完成bash的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在pash完成中处理空格和引号的正确/最佳方法是什么?



这是一个简单的例子。我有一个名为的单词(例如,字典查找程序),将各种单词作为参数。支持的单词实际上可能包含空格,并且在名为的文件中定义了word.dat

  foo 
bar one
bar two

这是我第一个建议的解决方案:

  _find_words()
{
search =$ cur
grep - ^ $ searchwords.dat
}

_words_complete()
{
本地IFS = $'\\\
'

COMPREPLY =()
cur =$ {COMP_WORDS [COMP_CWORD]}

COMPREPLY =($(compgen -W$(_ find_words) - $ cur))

}
complete -F _words_complete words

键入'单词f< tab>'正确完成命令到'foo'(带有尾部空格) ,这是很好的,但是对于的话b< tab>'它建议'单词栏'。正确的完成将是'单词bar\'。而对于'单词b< tab>''单词'b< tab>'它不提供任何建议。



最后一部分我已经能够解决了,可以使用 eval 正确解析(转义)但是, eval 不喜欢丢失的引号,所以为了让所有的工作,我不得不更改 search =$ cur to

  search = $(eval echo$ cur2> / dev / null || 
eval echo$ cur'2> / dev / null ||
eval echo$ cur\2> / dev / null ||)

这实际上是有效的。正确地自动填充,而且如果我有这样的话,那么添加一个'o'并再次按< tab> ,它实际上完成了这个词,并添加了正确的结束报价但是,如果我尝试完成的话b< tab>'甚至'的话语bar\< tab>',它自动完成到'单词栏'而不是'单词bar\',并添加现在,显然,可以正确地处理这个问题,例如, ls 命令可以对已命名的文件执行'foo' 'bar one''bar two'当使用'的<(有效)组合时,确实存在一些表达文件名的方法的问题。和各种逃脱)。但是,我无法弄清楚 ls 是如何通过阅读bash完成代码来实现的。



所以,任何人知道如何妥善处理这个?实际输入引号不需要保留;我会很乐意改变的单词b< tab>''单词'b< tab>''b< tab>' to 'words bar\',例如(虽然我更喜欢剥离引号,就像在这个例子中,而不是添加它们)。

解决方案

这个不太优雅的后处理解决方案似乎为我工作(GNU bash,版本3.1.17(6)-release(i686-pc-cygwin))(除非我没有像往常一样测试一些边界:))



不需要eval东西,只有两种引号。



由于compgen不想为我们逃避空间,我们将逃避他们自己(只有当一个词没有从一个报价开始),这有一个完整的列表(双标签)的副作用,具有转义值,不知道这是否好,因为ls不这样做...



编辑:固定为处理单词和双重句子内的单词其实我们必须通过3个unescapings :)。第一个为grep,第二个为compgen,最后为单词命令本身,当自动完成。

  _find_words()
{
search = $(eval echo$ cur2> / dev / null || eval echo$ cur'2> / dev / null || eval echo$ cur\2& dev / null ||)
grep - ^ $ searchwords.dat | sed -e{-e's#\\#\\\\ '-e's#'#\\\\''#g-e's##\\\#g'-e}
}

_words_complete()
{
local IFS = $'\\\
'

COMPREPLY =()
local cur =$ {COMP_WORDS [COMP_CWORD]

COMPREPLY =($(compgen -W$(_ find_words) - $ cur))

local escaped_single_qoute ='\''
local i = 0
$ {COMPREPLY [*]}
do
如果[[$ {cur:0:1}=='] ]
然后
#以单引号开头,仅转义其他单引号
#['] bla'blabla\\血乳酸血乳酸 - > ['] bla'\\''blabla\bla bla
COMPREPLY [$ i] =$ {entry // \'/ $ {escaped_single_qoute}}
elif [[ $ {cur:0:1}==\]]
然后
#以双引号开头,转义所有双引号和所有反斜杠
#[] bla' blabla\bla bla - > [] bla'bla\bla\\bla bla
entry =$ {entry // \\ / \\\\}
COMPREPLY [$ i] =$ {entry // \/ \\\}
else
#前面没有引号,转义_everything_
#[] bla'bla bla\bla bla - > [] bla\'bla\bla\\bla\ bla
entry =$ {entry // \\ / \\\\ \\\}
entry =$ {entry // \'/ \'}
entry =$ {entry // \/ \\\}
COMPREPLY [$ i] =$ {entry // / \\}
fi
((i ++))
done
}


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 dictionary lookup program) that takes various words as arguments. The supported ‘words’ may actually contain spaces, and are defined in a file called words.dat:

foo
bar one
bar two

Here’s my first suggested solution:

_find_words()
{
search="$cur"
grep -- "^$search" words.dat
}

_words_complete()
{
local IFS=$'\n'

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

COMPREPLY=( $( compgen -W "$(_find_words)" -- "$cur" ) )

}
complete -F _words_complete words

Typing ‘words f<tab>’ correctly completes the command to ‘words foo ’ (with a trailing space), which is nice, but for ‘words b<tab>’ it suggests ‘words bar ’. The correct completion would be ‘words bar\ ’. And for ‘words "b<tab>’ and ‘words 'b<tab>’ it offers no suggestions.

This last part I have been able to solve. It’s possible to use eval to properly parse the (escaped) characters. However, eval is not fond of missing quotes, so to get everything to work, I had to change the search="$cur" to

search=$(eval echo "$cur" 2>/dev/null ||
eval echo "$cur'" 2>/dev/null ||
eval echo "$cur\"" 2>/dev/null || "")

This actually works. Both ‘words "b<tab>’ and ‘words 'b<tab>’ correctly autocompletes, and if I add a ‘o’ and press <tab> again, it actually completes the word and adds the correct closing quote. However, if I try to complete ‘words b<tab>’ or even ‘words bar\ <tab>’, it is autocompleted to ‘words bar ’ instead of ‘words bar\ ’, and adding for instance ‘one’ would fail when the words program is run.

Now, obviously it is possible to handle this correctly. For instance, the ls command can do it for files namned ‘foo’ ‘bar one’ and ‘bar two’ (though it does have problems with some ways of expressing the filenames when one uses a (valid) combination of both ", ' and various escapes). However, I couldn’t figure out how ls does it by reading the bash completion code.

So, does anybody know of how properly handle this? The actual input quotes need not be preserved; I would be happy with a solution that changes ‘words "b<tab>’, ‘words 'b<tab>’ and ‘words b<tab>’ to ‘words bar\ ’, for instance, (though I would prefer stripping of quotes, like in this example, instead of adding them).

解决方案

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
}

这篇关于正确处理空白和报价完成bash的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆