考虑一下代码(变量$i
在那里,因为它处于循环中,在模式中添加了几个条件,例如*.a
和*.b
,...但是为了说明这一点问题,只有一个通配符模式就足够了):
#!/bin/bash
i="a"
PATTERN="-name bar -or -name *.$i"
find . \( $PATTERN \)
如果在包含文件bar
和foo.a
的文件夹上运行,它将工作,并输出:
./foo.a
./bar
但是如果您现在将一个新文件添加到该文件夹中,即zoo.a
,则它将不再起作用:
find: paths must precede expression: zoo.a
大概是因为*.$i
中的通配符被shell扩展到foo.a zoo.a
,这导致无效的find
命令模式。因此,一种解决方法是将引号括在通配符模式周围。除非它不起作用:
用单引号引起来– PATTERN="-name bar -or -name '*.$i'"
find
命令仅输出bar
。转义单引号(\'
)会得到相同的结果。
同义双引号:PATTERN="-name bar -or -name \"*.$i\""
-仅返回bar
。
find
命令中,如果将$PATTERN
替换为"$PATTERN"
,则会出现错误(对于单引号,则是相同的错误,但通配符模式周围的单引号) :
查找:未知谓词-name bar -or -name "*.a"'
当然,用$PATTERN
替换'$PATTERN'
也不起作用...(不会进行任何扩展)。
让它起作用的唯一方法是使用... eval
!
FINDSTR="find . \( $PATTERN \)"
eval $FINDSTR
这正常工作:
./zoo.a
./foo.a
./bar
现在经过大量的搜索,我看到它多次提到要做这种事情,一个人应该使用数组。但这不起作用:
i="a"
PATTERN=( -name bar -or -name '*.$i' )
find . \( "${PATTERN[@]}" \)
# result: ./bar
在find
行中,必须将数组用双引号引起来,因为我们想要对其进行扩展。但是通配符表达式周围的单引号不起作用,也没有引号:
i="a"
PATTERN=( -name bar -or -name *.$i )
find . \( "${PATTERN[@]}" \)
# result: find: paths must precede expression: zoo.a
但要完成双重报价!
i="a"
PATTERN=( -name bar -or -name "*.$i" )
find . \( "${PATTERN[@]}" \)
# result:
# ./zoo.a
# ./foo.a
# ./bar
所以我想我的问题实际上是两个问题:
a)在最后一个使用数组的示例中,为什么在*.$i
周围需要双引号?
b)以这种方式使用数组应该扩展«to all elements individually quoted»。如何使用变量(参见我的第一次尝试)?使它起作用后,我回去尝试再次使用带斜杠单引号或\\'
的变量,但没有任何效果(我刚得到bar
)。我该怎么做才能像“手工”一样模仿使用数组时的引用?
预先感谢您的帮助。
答案 0 :(得分:2)
必读:
a)在最后一个使用数组的示例中,为什么在
*.$i
周围需要双引号?
您需要使用某种形式的引号来防止Shell对*
执行glob扩展。变量不会用单引号引起来,因此'*.$i'
不起作用。它确实抑制了球体膨胀,但也停止了变量膨胀。 "*.$i"
禁止全局扩展,但允许变量扩展,这是完美的。
要真正深入研究细节,您需要在此处做两件事:
*
以防止全局扩展。$i
视为变量扩展,但请引用它以防止单词分裂和glob扩展。对于项目1:\*
,"*"
,'*'
和$'*'
的任何形式的报价都可以用来确保将其视为文字星号。
对于项目2,双引号是唯一的答案。裸露的$i
会出现单词分裂和模糊现象-如果您使用i='foo bar'
或i='foo*'
,则空格和模糊斑点会引起问题。 \$i
和'$i'
都按字面意义对待美元符号,所以它们不在了。
"$i"
是唯一可以正确执行所有操作的报价。这就是为什么通常的shell建议是总是双引号变量扩展。
最终结果是,以下任何一项都可以工作:
"*.$i"
\*."$i"
'*'."$i"
"*"."$i"
'*.'"$i"
很明显,第一个是最简单的。
b)以这种方式使用数组应该扩展«到所有单独引用的元素»。如何使用变量(参见我的第一次尝试)?使它起作用后,我回去尝试再次使用带斜杠单引号或
\\'
的变量,但没有任何效果(我刚得到bar
)。我该怎么做才能像“手工”一样模仿使用数组时的引用?
您必须将eval
拼凑到一起,但这很危险。从根本上说,数组比简单的字符串变量更强大。引号和反斜杠之间没有神奇的组合,可以让您做数组可以做的事情。数组是完成这项工作的正确工具。
您能否详细解释一下,为什么...
PATTERN="-name bar -or -name \"*.$i\""
不起作用?在实际运行find
命令时,带引号的双引号应扩展$i
而不是全局名称。
好的。假设我们写:
i=a
PATTERN="-name bar -or -name \"*.$i\""
find . \( $PATTERN \)
运行前两行后,$PATTERN
的值是多少?让我们检查一下:
$ i=a
$ PATTERN="-name bar -or -name \"*.$i\""
$ printf '%s\n' "$PATTERN"
-name bar -or -name "*.a"
您会注意到$i
已被a
取代,反斜杠也已被删除。
现在,让我们看看如何精确解析find
命令。在最后一行$PATTERN
中没有引用,因为我们希望所有单词都被分开,对吗?如果您写一个简单的变量名,Bash最终会执行隐式的 split + glob 操作。它执行单词拆分和全局扩展。到底是什么意思?
让我们看一下Bash如何执行命令行扩展。在“扩展”部分下的Bash man page中,我们可以看到操作顺序:
让我们手动进行这些操作,看看如何解析find . \( $PATTERN \)
。最终结果将是一个字符串列表,因此我将使用类似于JSON的语法显示每个阶段。我们将从一个包含单个字符串的列表开始:
['find . \( $PATTERN \)']
作为第一步,整个命令行都将进行分词。
['find', '.', '\(', '$PATTERN', '\)']
括号扩展-不变。
可变扩展
['find', '.', '\(', '-name bar -or -name "*.a"', '\)']
$PATTERN
被替换。目前,它都是单个字符串,空格和全部。
分词
['find', '.', '\(', '-name', 'bar', '-or', '-name', '"*.a"', '\)']
shell会扫描双引号中未出现的用于变量拆分的变量扩展结果。 $PATTERN
未引用,因此已展开。现在是一堆个别的单词。到目前为止一切顺利。
全局扩展
['find', '.', '\(', '-name', 'bar', '-or', '-name', '"*.a"', '\)']
Bash扫描单词拆分结果以查找glob。不是整个命令行,只有令牌-name
,bar
,-or
,-name
和"*.a"
。
看起来什么也没发生,是吗?没那么快!人不可貌相。 Bash实际上执行了glob扩展。碰巧,这个问题与任何事物都不匹配。但这可能... †
删除行情
['find', '.', '(', '-name', 'bar', '-or', '-name', '"*.a"', ')']
反斜杠不见了。但是双引号还在那里。
在前面的扩展之后,不是上述扩展之一引起的字符
\
,'
和"
的所有未加引号的出现删除。
这就是最终结果。双引号仍然存在,因此不是搜索名为*.a
的文件,而是搜索名称中带有文字双引号字符的名为"*.a"
的文件。搜索肯定会失败。
添加一对转义引号\"
根本没有达到我们想要的目的。引号并没有像他们应该的那样消失,并中断了搜索。不仅如此,而且他们也没有像应有的那样抑制水珠。
TL; DR —解析变量内的引号 与变量内引号 的方式不同。
†前四个标记没有特殊字符。但是最后一个"*.a"
可以。该星号是通配符。如果您仔细阅读手册页的“路径名扩展”部分,您会发现这里没有提及引号被忽略的情况。双引号不不保护星号。
等一下!什么?我以为引号会抑制全局扩展!
通常会这么做。如果用手写引号,它们确实会阻止全局扩展。但是,如果将它们放在一个无引号的变量中,它们就不会。
$ touch 'foobar' '"foobar"'
$ ls
foobar "foobar"
$ ls foo*
foobar
$ ls "foo*"
ls: foo*: No such file or directory
$ var="\"foo*\""
$ echo "$var"
"foo*"
$ ls $var
"foobar"
仔细阅读。如果我们创建一个名为"foobar"
的文件-即它的文件名中包含文字双引号-那么ls $var
将输出"foobar"
。该glob会展开,并与(公认的)文件名匹配!
为什么报价没有帮助?好吧,这种解释是微妙而棘手的。手册页显示:
分词之后……bash扫描每个单词中的字符
*
,?
和[
。
Bash每次执行单词拆分操作它还会扩展单词范围。还记得我怎么说无引号的变量受隐式 split + glob 运算符的约束吗?这就是我的意思。分裂和球体并存。
如果您写ls "foo*"
,则引号可防止foo*
分裂和浮现。但是,如果您编写ls $var
,则$var
会被展开,拆分和扩展。它没有被双引号引起来。 包含双引号并不重要。等到双引号出现时,为时已晚。单词拆分已经完成,因此遍历也完成了。