【问题标题】:Bash doesn't parse quotes when converting a string to arguments将字符串转换为参数时,Bash 不解析引号
【发布时间】:2013-03-29 05:34:06
【问题描述】:

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

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

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

【问题讨论】:

标签: string bash arguments spaces quoting


【解决方案1】:

发生这种情况的原因是 shell 解析命令行的顺序:它解析(并删除)引号和转义,然后替换变量值。当$testOne "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

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

【讨论】:

    【解决方案2】:

    现在我们有了 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]}"
    

    【讨论】:

    • echo 的使用在这里增加了一些错误,echo 带有一个未引用的参数加倍如此。如果$t=$'\t'; qs_parse "One \"*\" -n \"tab${t}between${t}each${t}word\"",您不希望这些制表符变成空格,或者将-n 视为echo 的参数。请考虑使用printf '%s\n' "$1"
    • @Grief,你能接受我之前提议的修改吗?
    • @CharlesDuffy 很抱歉没有早点这样做,但我已经更新了答案。似乎必须在任何地方用 printf 替换 echo 才能使“-n”工作。希望你喜欢现在的答案。谢谢!
    【解决方案3】:

    解决这个问题的方法是使用 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
    

    【讨论】:

    • xargs 绝对是适合这项工作的工具。未引用的扩展,不是那么多。如果您有test='One "This is two" Three "*"',您不希望将* 替换为文件名列表。
    • @CharlesDuffy 你不希望引用的* 被扩展,对吗?
    • @ivan_pozdeev,问题是arr=( $(...) ) 正在扩展$(...) 的结果是一个未引用 上下文,所以即使它在... 命令中被引用,它稍后在替换操作期间被扩展。你为什么不实际测试我抱怨的代码,而不是仅仅暗示抱怨是没有根据的?
    • @ivan_pozdeev, ...参见上面在replit.com/@CharlesDuffy2/UncomfortableIllDecompiler#main.sh中演示的内容
    • @CharlesDuffy 我误解了你的第一条消息——我认为它没有像你期望的那样扩展(所以我没有理由检查它)。感谢您的解释和演示,我现在看到了。
    【解决方案4】:

    我为此编写了几个本机 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 变量还可用于指定除空格之外的字段分隔符。

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

    【讨论】:

      【解决方案5】:
      test='One "This is two" Three'
      mapfile -t some_args < <(xargs -n1 <<<"$test")
      echo "'${some_args[0]}'" "'${some_args[1]}'" "'${some_args[2]}'"
      

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

      【讨论】:

        猜你喜欢
        • 2014-01-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-07-19
        • 2012-12-27
        • 1970-01-01
        相关资源
        最近更新 更多