我的 shell 是 zsh。我在 bash 上尝试了这些命令,成功了。
ignore_files=(
local
\*.local.\*
.bundle
Gemfile.lock
)
ignore_command=""
for f in "${ignore_files[@]}"; do
ignore_command="$ignore_command -x $f"
done
echo $ignore_command
# print: -x local -x *.local.* -x .bundle -x Gemfile.lock
sudo diff -ruNd $ignore_command <path a> <path-b>
# failed: diff: extra operand '<path-b>'
echo sudo diff -ruNd $ignore_command <path a> <path-b>
# sudo diff -ruNd -x local -x *.local.* -x .bundle -x Gemfile.lock <path a> <path-b>
eval diff -ruNd $ignore_command <path a> <path-b> # success
该变量似乎没有在该命令中扩展。我想知道为什么以及如何让它像 bash 一样而不需要 eval。
答案1
命令是参数列表,您想使用一个数组来存储这些参数:
ignore_files=(
local
'*.local.*'
.bundle
Gemfile.lock
)
ignore_command=()
for f in "${ignore_files[@]}"; do
ignore_command+=(-x "$f")
done
print -r -- "${(q+)ignore_command[@]}"
sudo diff -ruNd "${ignore_command[@]}" <path a> <path-b>
您可能对类似 Bourne 的 shell 的行为感到困惑,其中不带引号的参数扩展是 split+glob 运算符,因此它将字符串/标量变量转换为列表(或匹配模式的文件)扩展后。
这种不当行为已被zsh
大多数其他现代 shell 修复,如rc
, es
, fish
。
您可以使用 来模拟 Bourne shell 的行为set -o shwordsplit -o globsubst
,但这会导致倒退。具体来说,globsubst
在您的情况下会出现问题,因为*.local.*
包含全局字符。
在 中zsh
,如果你想分割一个变量或在其扩展时执行通配符,你必须明确地询问它:
$=var
,$var
按$IFS
字符分割。${(s:,:)var}
根据特定字符串进行分割(此处,
)$~var
:在扩展时执行通配符(或根据上下文进行模式匹配)(例如,如果$var
contains*
,$~var
则列表上下文中将扩展为当前目录中的文件列表,如 Bourne shell 的$var
)$=~var
:同时进行分割和通配符,就像$var
Bourne shell 的一样。
在这种特殊情况下,您还可以执行以下操作:
sudo diff -ruNd --exclude=$^ignore_files <path a> <path-b>
(其中^
导致数组像 infish
或rc
as --exclude=element1
--exclude=element2
... 一样扩展,它实际上启用了rcexpandparam
该扩展的选项,例如~
启用globsubst
和=
shwordsplit
)。