变量未在具有 diff 的命令中展开

变量未在具有 diff 的命令中展开

我的 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:在扩展时执行通配符(或根据上下文进行模式匹配)(例如,如果$varcontains *$~var则列表上下文中将扩展为当前目录中的文件列表,如 Bourne shell 的$var
  • $=~var:同时进行分割和通配符,就像$varBourne shell 的一样。

在这种特殊情况下,您还可以执行以下操作:

sudo diff -ruNd  --exclude=$^ignore_files <path a> <path-b>

(其中^导致数组像 infishrcas --exclude=element1 --exclude=element2... 一样扩展,它实际上启用了rcexpandparam该扩展的选项,例如~启用globsubst= shwordsplit)。

相关内容