为什么grep会忽略包含要忽略的目录的shell变量?
Why does grep ignore the shell variable containing directories to be ignored?
在 Mac OS X 上,我有一个这样的 bash 脚本:
# Directories excluded from grep go here.
EXCLUDEDIR="--exclude-dir={node_modules,.git,tmp,angular*,icons,server,coffee}"
# This grep needs to include one line below the hit.
grep -iIrn -A1 $EXCLUDEDIR -e "class=[\"\']title[\"\']>$" -e "<div class=\"content" . > microcopy.txt
但是好像忽略了$EXCLUDEDIR
。如果我只是直接使用 --exclude-dir
,它就可以工作。为什么它不能扩展变量并正常工作?
大括号在技术上是一个错误。当它们在变量中时,它们被逐字包含,而当您将它们作为命令的一部分直接键入时,Bash 执行大括号扩展,并有效地从表达式中删除大括号。
bash$ echo --exclude-dir=moo{bar,baz}
--exclude-dir=moobar --exclude-dir=moobaz
bash$ x='moo{bar,baz}'
bash$ echo --exclude-dir=$x
--exclude-dir=moo{bar,baz}
解决方法(不是那么简单)是显式列出您的参数。这可以通过使用数组列出要排除的目录名称来稍微简化(但这不能移植到旧版 /bin/sh
)。
x=(node_modules .git tmp angular\* icons server coffee)
EXCLUDEDIR="${x[@]/#/--exclude-dir=}"
angular\*
中的反斜杠是将此通配符表达式传递给未扩展的 grep
——如果 shell 扩展变量,grep
将不排除目录匹配子目录中的通配符表达式(除非它们恰好匹配当前目录中的扩展值之一)。如果 nullglob
有效,未转义的通配符将从列表中消失。
@tripleee 正确地描述了问题,但有两种解决方法我认为比使用数组更简单(而且我认为更便携):在 git
命令中使用 eval
,或在变量赋值本身中使用 echo
。 echo
方法更可取。
使用eval
# Directories excluded from grep go here.
EXCLUDEDIR="--exclude-dir={node_modules,.git,tmp,angular*,icons,server,coffee}"
# This grep needs to include one line below the hit.
eval grep -iIrn -A1 $EXCLUDEDIR # .... etc
这会导致大括号展开,就好像它们是按字面键入的一样。但是请注意,如果您不小心,它可能会产生一些意想不到的副作用;例如,您可能需要添加一些额外的 \
来转义引号和 $
符号。
使用echo
这可能比 eval
更安全,因为您不会意外执行隐藏在 EXCLUDEDIR
变量中的代码。
# Directories excluded from grep go here.
EXCLUDEDIR="$(echo --exclude-dir={node_modules,.git,tmp,angular*,icons,server,coffee})"
# This grep needs to include one line below the hit.
grep -iIrn -A1 $EXCLUDEDIR # .... etc
在 Mac OS X 上,我有一个这样的 bash 脚本:
# Directories excluded from grep go here.
EXCLUDEDIR="--exclude-dir={node_modules,.git,tmp,angular*,icons,server,coffee}"
# This grep needs to include one line below the hit.
grep -iIrn -A1 $EXCLUDEDIR -e "class=[\"\']title[\"\']>$" -e "<div class=\"content" . > microcopy.txt
但是好像忽略了$EXCLUDEDIR
。如果我只是直接使用 --exclude-dir
,它就可以工作。为什么它不能扩展变量并正常工作?
大括号在技术上是一个错误。当它们在变量中时,它们被逐字包含,而当您将它们作为命令的一部分直接键入时,Bash 执行大括号扩展,并有效地从表达式中删除大括号。
bash$ echo --exclude-dir=moo{bar,baz}
--exclude-dir=moobar --exclude-dir=moobaz
bash$ x='moo{bar,baz}'
bash$ echo --exclude-dir=$x
--exclude-dir=moo{bar,baz}
解决方法(不是那么简单)是显式列出您的参数。这可以通过使用数组列出要排除的目录名称来稍微简化(但这不能移植到旧版 /bin/sh
)。
x=(node_modules .git tmp angular\* icons server coffee)
EXCLUDEDIR="${x[@]/#/--exclude-dir=}"
angular\*
中的反斜杠是将此通配符表达式传递给未扩展的 grep
——如果 shell 扩展变量,grep
将不排除目录匹配子目录中的通配符表达式(除非它们恰好匹配当前目录中的扩展值之一)。如果 nullglob
有效,未转义的通配符将从列表中消失。
@tripleee 正确地描述了问题,但有两种解决方法我认为比使用数组更简单(而且我认为更便携):在 git
命令中使用 eval
,或在变量赋值本身中使用 echo
。 echo
方法更可取。
使用eval
# Directories excluded from grep go here.
EXCLUDEDIR="--exclude-dir={node_modules,.git,tmp,angular*,icons,server,coffee}"
# This grep needs to include one line below the hit.
eval grep -iIrn -A1 $EXCLUDEDIR # .... etc
这会导致大括号展开,就好像它们是按字面键入的一样。但是请注意,如果您不小心,它可能会产生一些意想不到的副作用;例如,您可能需要添加一些额外的 \
来转义引号和 $
符号。
使用echo
这可能比 eval
更安全,因为您不会意外执行隐藏在 EXCLUDEDIR
变量中的代码。
# Directories excluded from grep go here.
EXCLUDEDIR="$(echo --exclude-dir={node_modules,.git,tmp,angular*,icons,server,coffee})"
# This grep needs to include one line below the hit.
grep -iIrn -A1 $EXCLUDEDIR # .... etc