bash - 如何使用带参数的字符串来调用脚本,就像 bash 在解释命令行时所做的那样?

标签 bash arguments variable-expansion parameter-expansion

期望的结果

有没有办法使用包含参数的字符串来调用脚本

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(不带双引号)调用即可让我们回到第一次尝试的结果(进一步)。

研究

围绕这个特定问题有一些很好的文档和帖子。只提一些:

  1. 这个FAQ建议使用参数构建一个数组(如本问题中所述)和 expanding它的元素,关于参数扩展的一些注意事项,IFS= read -r ramblings以及其他一些不相关的例子。深入研究将数组参数传递给 bash 脚本this answer清楚地反射(reflect)了这一点,但对于某些在某些情况下可能有效的解决方法,这不是一个得到良好支持的方法(它还引用 bash author's 引用:实际上没有一种好的方法来编码数组 变量到环境中)。

  2. 这个answer (bash 将带引号的行分割为参数),清楚地反射(reflect)了 shell 处理命令与字符串的方式的差异。 shell 执行 quote removal作为执行命令之前的最后一步。相反,在解释 str_params 变量时,shell 将引号视为另一个字符,因此,它们不会删除引号嗯>。最令人惊讶的是,在another answer中在同一篇文章中,似乎有一些重写参数列表的示例(无论这是否可以是通用方法)。

  3. 这个其他 answer (如何迭代 bash 脚本中的参数)解释了shell 在扩展变量之前处理引号,并让 shell 注意引号 em> 在变量(上面示例中的str_paramscmd)中,需要使用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 中的 xargsshlex.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/

相关文章:

参数数量不限的 PHP 函数

linux - 在 Bash 中,有没有办法用双引号将变量展开两次?

bash - 为什么子串变量扩展可以引用没有美元符号的变量?

bash 脚本将参数回显到单独的行中

linux - while 循环读取命令正在打印位置而不仅仅是文件名 - 这是为什么?

javascript - JSLint 意外 'arguments'

linux - Bash 脚本 : expansion of argument not using $@ or $*

linux - 使用正则表达式重命名一组文件的最佳方法是什么?

node.js - 无法在一台计算机上的 git bash 中运行 "npm start",但在另一台计算机上我可以

java - 将参数传递给重载函数的一些问题