如何转移参数并将其完全从 args ls 中抛出?

如何转移参数并将其完全从 args ls 中抛出?

我有一个获取 N 个参数的脚本。它首先解析这些参数(提取特定值X),然后使用该值调用另一个程序:

function main() {
    parse "$@"
    run "$@"
}
main "$@"

我想在参数列表的开头添加一个可选参数(用于设置我正在运行的 Python 版本的参数)。因此,我将其添加到解析函数中:

if [ "$1" = '2' ] || [ "$1" = '3' ]; then version=$1 && shift; else version=2; fi

但是,解析结束后,我陷入了困境,因为shift不会影响运行函数。我怎样才能做到这一点而不再次检查第一个值的值(这将违背解析函数的整个目的)

答案1

每个 shell 函数的作用域(以及脚本中顶层代码的作用域)都有自己的位置参数,并且只能直接访问其自身。最干净的解决方案是将您感兴趣的位置参数的值放入一个大批。然后您可以在多个函数作用域中读取并修改该数组。

例如,您可以让此代码出现在脚本的开头附近:

declare -a args=("$@")
  • 随意命名该数组——它不一定被称为args
  • declare -a如果愿意的话,您甚至可以省略。
  • 它实际上不需要出现在开头附近。它只需要跑步在任何访问的代码args运行之前。它甚至可以出现在使用 的 shell 函数定义下方args。为了清楚起见,我建议将其放在开头附近。

然后,您可以从多个函数对数组进行操作args。您不必将其传递给函数;它们已经能够访问它。当 shell 函数中的代码修改内容args然后返回时,调用者中的代码将能够观察到更改。

包括 Bash 在内的 Bourne 风格 shell 中的位置参数使用基于 1 的索引。(这是因为$0,它会扩展为程序名称,从技术上讲它不是位置参数,并且在函数作用域中不会发生变化。)但 Bash 中的数组使用基于 0 的索引。因此,在 之后args=("$@")$1matches ${args[0]}$2matches ${args[1]}$3matches${args[2]}等等。$@仍然会像您预期的那样匹配${args[@]}

为了便于阅读,我这样编写了它们,没有使用引号。当然,您几乎总是希望对涉及args数组的扩展使用双引号,就像您几乎总是希望对涉及位置参数的扩展使用双引号一样。

如果您决定采用这种方法,那么不要:

shift

你会写:

args=("${args[@]:1}")

如果你刚接触 Bash 中的数组,你可能想看看Bash 参考手册的相关部分。您可能还想进行交互式实验。例如:

ek@Cord:~$ args=('foo bar' 'baz quux' 'ham spam')
ek@Cord:~$ printf '[%s]\n' "${args[@]}"
[foo bar]
[baz quux]
[ham spam]
ek@Cord:~$ printf '[%s]\n' "${args[@]:1}"
[baz quux]
[ham spam]

对应的代码

if [ "$1" = 2 ] || [ "$1" = 3 ]; then
    version="$1"
    shift
else
    version=2
fi

将会:

if [ "${args[0]}" = 2 ] || [ "${args[0]}" = 3 ]; then
    version="${args[0]}"
    args=("${args[@]:1}")
else
    version=2
fi

使用数组在语法上更麻烦,但也更灵活。

另一方面,由于您似乎已将脚本的大部分代码组织成一个run函数及其调用者,因此您可以考虑解析任何特殊命令行参数的替代方法在任何 shell 函数之外调用run,然后run "$@"像已经在做的那样调用。

有些编程语言的相关文化有着强烈的道德观念,即把几乎所有的东西都放入小型的、独立的函数中。Bash 不是这样的语言,从 shell 函数返回复杂数据的方式有限就是原因之一。您不应该害怕编写 shell 函数,甚至应该愿意编写大量的小型 shell 函数。但我认为,如果您的脚本的最佳形式是其他形式,您也不必担心。

如需进一步阅读,以及一些替代方法(包括获取流程替换输出的奇怪方法),请参阅Gilles 的回答函数调用者位置参数

相关内容