15

这是我的问题。在 bash 3 中:

$ test='One "This is two" Three'
$ set -- $test
$ echo $2
"This

如何让 bash 理解引号并将 $2 作为This is two而不是返回"This?不幸的是,我无法更改test此示例中调用的变量的构造。

4

5 回答 5

14

发生这种情况的原因是由于 shell 解析命令行的顺序:它解析(并删除)引号和转义,然后替换变量值。当时间$test被替换为 时One "This is two" Three,引号达到预期效果为时已晚。

执行此操作的简单(但危险)方法是添加另一个级别的解析eval

$ test='One "This is two" Three'
$ eval "set -- $test"
$ echo "$2"
This is two

(请注意,echo命令中的引号不是必需的,但这是一个很好的一般做法。)

我说这很危险的原因是它不只是返回并重新解析引用的字符串,它会返回并重新解析所有内容,可能包括您不希望解释为命令替换的内容。假设你已经设置

$ test='One `rm /some/important/file` Three'

...eval实际上将运行该rm命令。因此,如果您不能指望 的内容$test是“安全的”,请不要使用此构造

顺便说一句,做这种事情的正确方法是使用数组:

$ test=(One "This is two" Three)
$ set -- "${test[@]}"
$ echo "$2"
This is two

不幸的是,这需要控制变量的创建方式。

于 2013-04-06T23:44:56.873 回答
6

现在我们有了 bash 4,可以在其中执行类似的操作:

#!/bin/bash

function qs_parse() { 
    readarray -t "$1" < <( printf "%s" "$2"|xargs -n 1 printf "%s\n" ) 
}

tab='   '  # tabulation here
qs_parse test "One 'This is two' Three -n 'foo${tab}bar'"

printf "%s\n" "${test[0]}"
printf "%s\n" "${test[1]}"
printf "%s\n" "${test[2]}"
printf "%s\n" "${test[3]}"
printf "%s\n" "${test[4]}"

输出,如预期:

One
This is two
Three
-n
foo     bar  # tabulation saved

实际上,我不确定,但在较旧的 bash 中可能会这样做:

function qs_parse() {
    local i=0
    while IFS='' read -r line || [[ -n "$line" ]]; do
        parsed_str[i]="${line}"
        let i++
    done < <( printf "%s\n" "$1"|xargs -n 1 printf "%s\n" )
}

tab='   ' # tabulation here
qs_parse "One 'This is two' Three -n 'foo${tab}bar'"

printf "%s\n" "${parsed_str[0]}"
printf "%s\n" "${parsed_str[1]}"
printf "%s\n" "${parsed_str[2]}"
printf "%s\n" "${parsed_str[3]}"
printf "%s\n" "${parsed_str[4]}"
于 2016-03-29T10:25:13.530 回答
4

这个问题的解决方案是使用 xargs (eval free)。
它将双引号字符串保留在一起:

$ test='One "This is two" Three'
$ IFS=$'\n' arr=( $(xargs -n1 <<<"$test") )
$ printf '<%s>\n' "${arr[@]}"
<One>
<This is two>
<Three>

当然,您可以使用该数组设置位置参数:

$ set -- "${arr[@]}"
$ echo "$2"
This is two
于 2016-05-23T03:23:46.663 回答
1

我写了几个本机 bash 函数来做到这一点:https ://github.com/mblais/bash_ParseFields

您可以ParseFields像这样使用该功能:

$ str='field1 field\ 2 "field 3"'
$ ParseFields -d "$str" a b c d
$ printf "|%s|\n|%s|\n|%s|\n|%s|\n" "$a" "$b" "$c" "$d"
|field1|         
|field 2|
|field 3|
||                

ParseFields的-d选项会删除任何周围的引号并解释解析字段中的反斜杠。

还有一个更简单的ParseField函数(由 使用ParseFields)解析字符串中特定偏移量的单个字段。

请注意,这些函数无法解析,只能解析字符串。IFS 变量还可用于指定除空格之外的字段分隔符。

如果您要求未转义的撇号可能出现在未引用的字段中,则需要稍作更改 - 让我知道。

于 2014-02-26T22:54:48.837 回答
0
test='One "This is two" Three'
mapfile -t some_args < <(xargs -n1 <<<"$test")
echo "'${some_args[0]}'" "'${some_args[1]}'" "'${some_args[2]}'"

输出:'一''这是二''三'

于 2020-04-28T07:18:13.453 回答