将引号存储在变量中以用作命令行参数 Bash

Mik*_*ike 6 bash quoting

我正在编写一个脚本来使用一些不同的参数多次调用命令(weka,如果你好奇的话)。参数之一,'-search "< stuff >" '需要引用。在某些情况下,我需要使用多个这些参数,例如。

weka a -search "a params" -search "other a params"

weka b -search "just these params"`
Run Code Online (Sandbox Code Playgroud)

我一直在尝试使用一个类似于以下内容的关联数组:

search=( ["a"]='-search "a params" -search "other a params"'
["b"]='-search "just these params"'
Run Code Online (Sandbox Code Playgroud)

然后像这样拨打电话:

function call_thing_with_the_right_parameters_so_it_works_out_alright {
    weka $1 ${search["$1"]}
} # <-- closing brace for the function
call_thing_with_the_right_parameters_so_it_works_out_alright
Run Code Online (Sandbox Code Playgroud)

唉,无论我尝试什么,引用都被搞砸了:

bash -x ./this_is_the_name_of_my_script_sorry_its_so_long
...
+ weka a -search '"a' 'params"' # <-- (not what I want)
...
Run Code Online (Sandbox Code Playgroud)

有任何想法吗?

小智 3

代码中的行将weka $1 ${search["$1"]}受到 shell 分割的影响。
如果您没有更改变量,$IFS则将在 上发生拆分spacetabnew line

该行扩展为:

weka $1 ${search["$1"]}
weka a -search "a params" -search "other a params"
Run Code Online (Sandbox Code Playgroud)

但是如上所述进行拆分,这就是它的含义:

<weka> <a> <-search> <"a> <params"> <-search> <"other> <a> <params">
Run Code Online (Sandbox Code Playgroud)

您可以看到在该行前面添加了完全相同的 printf:

$ printf '<%s> ' weka $1 ${search["$1"]}; echo
<weka> <a> <-search> <"a> <params"> <-search> <"other> <a> <params">
Run Code Online (Sandbox Code Playgroud)

如果正确引用变量,情况会更好:

$ printf '<%s> ' weka "$1" "${search["$1"]}"; echo
<weka> <a> <-search "a params" -search "other a params">
Run Code Online (Sandbox Code Playgroud)

但这不是你想要的。你需要分割它,但不是简单的空间。

该怎么办?

有两种解决方案:

手动拆分

使用一些字符#来手动标记分割位置:

search=( ["a"]='-search#a params#-search#other a params' ["b"]='-search#just these params' )
Run Code Online (Sandbox Code Playgroud)

然后告诉 bash 你用来分割的字符是哪个IFS,这样它就可以将字符串分割到一个新数组中b

IFS='#'
b=( ${search["a"]} )
printf '<%s> ' "${b[@]}"; echo
Run Code Online (Sandbox Code Playgroud)

其产生:

<-search> <a params> <-search> <other a params> 
Run Code Online (Sandbox Code Playgroud)

您想要的精确分割。
唯一的问题是它IFS被改变了,但我们可以在本地函数中解决这个问题IFS

callsplit(){
    local IFS='#'
    b=( ${search["$1"]} )
    weka "$1" "${b[@]}"
}
Run Code Online (Sandbox Code Playgroud)

使用评估

另一个解决方案是使用 eval 重新解析命令行,以便 shell 可以将其拆分,就像 shell 拆分行的常见方式一样。
变量搜索的值将如您所定义的那样:

search=( ["a"]='-search "a params" -search "other a params"'  
         ["b"]='-search "just these params"' )
Run Code Online (Sandbox Code Playgroud)

但我们将用 eval 扩展执行行:

eval weka "$1" "${search["$1"]}"
Run Code Online (Sandbox Code Playgroud)

如果您想查看该行如何展开,请使用以下命令:

$ eval printf "'<%s> '" weka "$1" "${search["$1"]}"; echo
<weka> <a> <-search> <a params> <-search> <other a params>
Run Code Online (Sandbox Code Playgroud)

整个脚本将是:

#!/bin/bash
declare -A search
search+=( ["a"]='-search "a params" -search "other a params"')
search+=( ["b"]='-search "just these params"' )

call_thing() {
    eval weka "$1" "${search["$1"]}"
} # <-- closing brace for the function

call_thing "a"
Run Code Online (Sandbox Code Playgroud)

注意:假设搜索值是在脚本设置的(外部攻击者无法设置它们),并且这些值被引用为通用 shell“命令行”,那么这将正确工作。

警告:使用 eval 可能允许将字符串形式的数据转换为像命令一样的代码。在这个特定的脚本中,这一行:

call_thing "a; touch new_file"
Run Code Online (Sandbox Code Playgroud)

就会执行命令touch new_file。但也可以执行任何其他命令。小心,非常小心你喂的东西eval

如上所述,请记住,有许多危险命令可以在 shell 中执行,例如rm -r /. 该命令eval并不比任何一个命令更强大。请小心。