期望的结果
有没有办法使用包含参数的字符串来调用脚本
?
str_params="this/one 'that one' and \"yet another\""
下面的函数在 stdout
上打印有关如何接收参数的反馈:
display_args () {
all_args=("${0}" "${@}")
for i in "${!all_args[@]}"; do
printf " $%d is '%q'\n" "${i}" "${!i}"
done
}
期望的结果如下,其中使用数组扩展(请参阅ary_params
使用,在str_params
的拆分中):
ary_params=(this/one 'that one' and "yet another")
display_args "${ary_params[@]}"
$0 is 'bash'
$1 is 'this/one'
$2 is 'that\ one'
$3 is 'and'
$4 is 'yet\ another'
问题
第一次尝试使用str_params
表明,无论单引号和双引号如何,字符串在转换为参数时都会被空格分割:
$ display_args ${str_params}
$0 is 'bash'
$1 is 'this/one'
$2 is '\'that'
$3 is 'one\''
$4 is 'and'
$5 is '\"yet'
$6 is 'another\"'
第二次尝试引发了一个我仍在尝试理解的错误。 bash
真的尝试查找一个名为所有该字符串的文件吗?
$ cmd="display_args ${str_params}"
$ echo "${cmd}"
display_args this/one 'that one' and "yet another"
$ "$cmd"
bash: display_args this/one 'that one' and "yet another": No such file or directory
- 仅使用
$cmd
(不带双引号)调用即可让我们回到第一次尝试的结果(进一步)。
研究
围绕这个特定问题有一些很好的文档和帖子。只提一些:
这个FAQ建议使用参数构建一个数组(如本问题中所述)和 expanding它的元素,关于参数扩展的一些注意事项,
IFS= read -r
ramblings以及其他一些不相关的例子。深入研究将数组参数传递给 bash 脚本,this answer清楚地反射(reflect)了这一点,但对于某些在某些情况下可能有效的解决方法,这不是一个得到良好支持的方法(它还引用 bash author's 引用:实际上没有一种好的方法来编码数组 变量到环境中)。这个answer (bash 将带引号的行分割为参数),清楚地反射(reflect)了 shell 处理命令与字符串的方式的差异。 shell 执行 quote removal作为执行命令之前的最后一步。相反,在解释
str_params
变量时,shell 将引号视为另一个字符,因此,它们不会删除引号嗯>。最令人惊讶的是,在another answer中在同一篇文章中,似乎有一些重写参数列表的示例(无论这是否可以是通用方法)。这个其他 answer (如何迭代 bash 脚本中的参数)解释了shell 在扩展变量之前处理引号,并让 shell 注意引号 em> 在变量(上面示例中的
str_params
和cmd
)中,需要使用eval
。然而使用eval
看起来更像是risky方法,我仍然怀疑是否有其他解决方案可以解决原则上看起来比这更容易的问题。
使用评估
按照上面的示例,我尝试了eval
并且似乎有效:
$ echo "$cmd"
display_args this/one 'that one' and "yet another"
$ eval $cmd
$0 is 'bash'
$1 is 'this/one'
$2 is 'that\ one'
$3 is 'and'
$4 is 'yet\ another'
$ eval display_args $str_params
$0 is 'bash'
$1 is 'this/one'
$2 is 'that\ one'
$3 is 'and'
$4 is 'yet\ another'
问题
- 有没有办法使用包含我们将使用的参数的字符串将它们传递给
脚本
而不使用评估
? - 或者,是否有一种直接的方法可以将字符串转换为参数的
数组
,看起来就像bash
会做的那样? (这将允许通过扩展数组的元素来调用,如上面这行所述)。
最佳答案
通常,xargs
可以做到这一点。 Xargs 无法处理引号内的换行符,换行符结束参数并开始另一行参数。例如,当您想要多次运行一个命令并在文件中的不同行上包含参数时,xargs
就是完美的工具。
$ echo "this/one 'that one' and \"yet another\"" | xargs -t printf "%q\n"
printf '%q\n' this/one 'that one' and 'yet another'
this/one
'that one'
and
'yet another'
最好的解决方案是首先不将参数存储在字符串中。将参数序列化为您可以在 Bash 中轻松阅读的内容。例如,每个参数位于单独的行或以零分隔。或者从 declare -p
输出。
另一个解决方案是不使用 Bash。将 python 与 shlex.split()
结合使用。
Is there a way to use a string containing the arguments we will use to pass them to a script without the use of eval?
是的,编写一个解析器将输入解析为参数。对于每个字符,进行标记,检测它是否是引号,处理转义序列,并自己构建参数数组。就像使用任何其他编程语言一样。或者使用完全可以完成该工作的外部现有程序。就像 Python 中的 xargs
或 shlex.split()
一样。
看起来很容易使用 xargs 将其加载到数组中:
$ readarray -t -d '' args < <(echo "this/one 'that one' and \"yet another\"" | xargs -t printf "%s\0"); declare -p args
printf '%s\0' this/one 'that one' and 'yet another'
declare -a args=([0]="this/one" [1]="that one" [2]="and" [3]="yet another")
使用 Python,您可以正确处理换行符,但如果您必须使用 Python 作为 Bash 脚本,您不妨将整个 Bash 重写为 Python:
$ readarray -t -d '' args < <(python -c 'import sys, shlex; print("\0".join(shlex.split(sys.argv[1])), end="")' "this/one 'that "$'\n'"one' and \"yet another\""); declare -p args
declare -a args=([0]="this/one" [1]=$'that \none' [2]="and" [3]="yet another")
Does bash really try to find a file called all that string?
是的,您引用了“$cmd”
,因此它是一个参数,并且cmd
的所有内容都是一个参数。因为它是行中的第一个,所以它是命令的名称。另请注意,未加引号的扩展会经历分词以及文件名扩展!幸运的是,您没有在字符串中使用 *
进行测试来注意到它。引用在 shell 中确实非常重要。请记住使用 shellcheck 检查您的脚本。
关于bash - 如何使用带参数的字符串来调用脚本,就像 bash 在解释命令行时所做的那样?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74261096/