使用包含引号的字符串作为 bash 中命令的参数,无需额外转义

Using a string which contains quotes as parameter to a command in bash without additional escaping

背景

我想做的是在查找命令中排除 git 存储库的所有子模块。我知道我可以像这样排除单个目录:

find . -not -path './$submodule/*'

所以我构建了一个生成大量这些语句并存储它们的命令:

EXCLUDES=$(for submodule in $(git submodule status | awk '{print }'); do
    echo -n "-not -path './$submodule/*' ";
done)

问题

但是当我运行find . $EXCLUDES时,这不起作用。我怀疑这是因为我不理解的 bash 引用策略。例如,假设 (# 标记输出):

tree .
# .
# ├── bar
# │   └── baz.scala
# └── foo.scala

set -x
EXCLUDES="-not -path './bar/*'"
find . -type f $EXCLUDES
# + find . -not -path ''\''./bar/*'\''' <---- weird stuff
# foo.scala
# baz.scala

find . -type f -not -path './bar/*'
# + find . -type f -not -path './bar/*'
# foo.scala

我如何告诉 bash 不要使用它所做的奇怪的引用(见上面标记的行)?

编辑:@eddiem 建议使用 git ls-files,我将在这个具体案例中这样做。但我仍然对在一般情况下如何执行此操作感兴趣,在这种情况下,我有一个带引号的变量并想将其用作命令的参数。

您注意到的 "weird stuff" 是因为 bash 仅扩展 $EXCLUDES 一次,方法是代入您存储在 EXCLUDES 中的值。它不会像在命令行上指定带引号的字符串时那样递归处理 EXCLUDES 的内容以删除单引号。相反,bash 转义 $EXCLUDES 中的特殊字符,假设您希望它们在那里:

-not -path './bar/*'

变成

-not -path ''\''./bar/*'\'''
             ^^         ^^ escaped single quotes
           ^^             ^^ random empty strings I'm actually not sure about
               ^       ^ single quotes around the rest of your text.

所以,正如@Jean-FrançoisFabre 所说,如果你在 EXCLUDES=... 中去掉单引号,你将不会得到奇怪的东西。

那么为什么第一个 find 没有按预期工作?因为 bash 将 $EXCLUDES 扩展为单个单词,即 argv 的单个元素被传递给 find.* 但是, find 期望它的参数是单独的词。结果,find 没有达到您的预期。

据我所知,执行此类操作的最可靠方法是使用数组:

declare -a EXCLUDES    #make a new array
EXCLUDES+=("-not" "-path" './bar/*')
    # single-quotes       ^       ^ so we don't glob when creating the array

并且您可以重复 += 行任意次数以排除您想要的内容。然后,使用这些:

find . -type f "${EXCLUDES[@]}"

"${name[@]}" 形式, 所有标点符号,将数组的每个元素扩展为一个单独的单词,但不会进一步扩展这些单词。所以 ./bar/* 会保持原样,不会被全局化。 (如果你确实想要 globbing,find . -type f ${EXCLUDES[@]}(没有 "")将扩展数组的每个元素。)

编辑 顺便说一下,要查看数组中的内容,请执行 set|grep EXCLUDES。您将每个元素单独列出。您也可以执行 echo "${EXCLUDES[@]}",但我发现这对调试用处不大,因为它不显示索引。

* 请参阅 the man page 的 "expansion" 部分。 "Parameter expansion," 扩展以 $ 开头的内容不能更改命令行上的单词数 — 除了 "$@""${name[@]}".