我有一个获取 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=("$@")
,$1
matches ${args[0]}
、$2
matches ${args[1]}
、$3
matches${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 的回答到函数调用者位置参数。