下面是我试图执行的函数。该问题似乎与main_cmd
另一个命令的别名有关。我认为这只是生成子 shell 时的问题,那么为什么简单的数组扩展会出现这种情况呢? IIUC 不会生成子 shell。
my_function() {
... code to set opt1 based on $1 ...
cmd=(
main_cmd hard_coded_arg_1 hard_coded_arg_2
-a "$opt1"
-b hard_coded_opt
"'$2'"
)
# Doesn't work.
"$cmd[@]"
# Works
eval "$cmd[@]"
}
我只想使用,eval
但这似乎是一种反模式(尽管我不明白为什么它仅对表达式很重要我将要执行直接地,但那是另一个话题了)。
编辑:澄清这是使用zsh
答案1
别名很特殊。它们在解析完全完成之前在命令行处理的早期进行处理,并且它们的工作方式更像是直接文本替换,而不是调用单独的函数。
使用别名,您可以执行以下操作:
% alias maybe=if
% maybe true; then echo yes; fi
yes
或者
% alias foo='echo hi; '
% foo echo bar
hi
bar
甚至
% alias xyz='echo "foo'
% xyz bar"
foo bar
在第二种情况下,foo echo bar
几乎只是被翻译成echo hi; echo bar
,然后以通常的方式进行解析。对于函数,它将被解析为命令名称和两个参数,然后 shell 稍后需要确定它foo
是函数还是内置命令或外部命令。在最后一种情况下,xyz bar"
如果别名不存在,则该命令将具有未闭合的引号。
上面的例子都不适用于函数。(好吧,无论如何,它们可以说是“工作”的。有人可能会说它们更像是休息shell 语法。)
但这也意味着在"${cmd[@]}"
解析和扩展之后,别名处理早已不复存在。事实上你可以这样做:
% alias runcmd='"${cmd[@]}"'
% cmd=(printf "'%s'\n" "foo bar" abc)
% runcmd
'foo bar'
'abc'
这应该可以表明在数组扩展完成之前已经处理了别名。
使用eval
,它“有效”,因为eval
强制进行另一轮命令行处理,包括别名扩展。但它还涉及处理命令参数中的任何引号,而您可能并不真正想要这样做。看Stéphane 的详细回答关于这一点。
使用函数main_cmd
而不是别名可能不会那么混乱。
答案2
您想要:
cmd=(
main_cmd hard_coded_arg_1 hard_coded_arg_2
-a "$opt1"
-b hard_coded_opt
"$2"
)
"${cmd[@]}"
我们定义一个包含以下元素的数组:
main_cmd
hard_coded_arg_1
hard_coded_arg_2
-a
value-of-opt1-variable
-b
hard_coded_opt
value-of-second-positional-parameter
然后"${cmd[@]}"
(或"$cmd[@]"
在 zsh 中)分别扩展为每个参数,因此我们最终运行从数组的第一个元素 ( ) 查找的命令,$cmd[1]
并将数组的所有元素作为参数。
在 zsh 中,使用 just$cmd
也可以在这里工作,但因为它删除了空元素,所以不能用于任意命令。
或者(尽管使用数组没有什么意义):
cmd=(
'main_cmd hard_coded_arg_1 hard_coded_arg_2'
'-a "$opt1"'
'-b hard_coded_opt'
'"$2"'
)
eval " ${cmd[@]}"
其中数组有:
main_cmd hard_coded_arg_1 hard_coded_arg_2
-a "$opt1"
-b hard_coded_opt
"$2"
我们将它们eval
作为单独的参数传递给(在第一个参数前面添加一个空格,以防它包含可以作为选项的 a -
)eval
,它与中间的空格连接起来给出:main_cmd hard_coded_arg_1 hard_coded_arg_2 -a "$opt1" -b hard_coded_opt "$2"
然后被解释为 shell 代码(因为那是做什么eval
)。
正在做:
cmd=( main_cmd hard_coded_arg_1 hard_coded_arg_2 -a "$opt1" -b hard_coded_opt "'$2'" ) eval "$cmd[@]"
是错误的,因为您得到的数组的元素如下:
main_cmd
hard_coded_arg_1
hard_coded_arg_2
-a
value-of-opt1-variable
-b
hard_coded_opt
'value-of-second-positional-parameter'
所以你最终会eval
解释:
main_cmd hard_coded_arg_1 hard_coded_arg_2 -a value-of-opt1-variable -b hard_coded_opt 'value-of-second-positional-parameter'
如果value-of-opt1-variable
碰巧存在$(reboot)
或$2
恰好包含,'; rm -rf ~ #
你可以想象会产生灾难性的后果。
请注意, in zsh 是使用其间的第一个字符连接数组元素的eval " $cmd"
缩写。这对于以空格开头的默认值来说没问题,但在已修改的上下文中使用时会中断(尽管在 zsh 中不常见,因为 zsh 具有适当的显式拆分和连接运算符,因此很少需要对其进行修改) )。eval " ${cmd[*]}"
$IFS
$IFS
$IFS
$IFS
如果这里的目的是通过将命令分成几行来使命令更容易阅读,那么几乎在每个 shell 中都有效的传统方法是使用行延续通过在行尾插入反斜杠:
my_function() {
main_cmd hard_coded_arg_1 hard_coded_arg_2 \
-a "$opt1" \
-b hard_coded_opt \
"$2"
}
通常会缩进行的延续部分,以使其更清楚地表明它们是延续部分,而不是单独的行。有些人更喜欢这样写:
my_function() {
main_cmd hard_coded_arg_1 hard_coded_arg_2 \
-a "$opt1" \
-b hard_coded_opt \
"$2"
}
这使得更容易发现 a\
缺失的情况。
无论如何,您可能不想在这里使用别名。别名有点像 C 预处理器宏,它们很早就被扩展了(在 C 中的预处理阶段,在 shell 中读取后、标记代码并部分解析其语法之后),但与 C 不同,只有在以下情况下才扩展:在发现指挥位置² 并且不要接受争论。
这就解释了为什么它没有扩展,因为在命令位置中找不到array=( alias_name ... )
它。alias_name
使用main_cmd() the command "$@"
代替alias main_cmd='the command'
没有问题,因为您实际上main_cmd
在那里定义了一个真正的命令,而不是一个令牌,该令牌意味着被一些文本替换,以便在某种条件下重新评估为代码。
1 在别名扩展之后,结果中的语法将被重新解析,这甚至可能完全改变命令的含义,并且可能会经历更多轮别名扩展。
² 尽管看到alias -g
全局zsh
别名,这些别名在全局范围内扩展,而不仅仅是在指挥位置