arrays - find 命令的参数扩展
问题描述
考虑代码(变量$i
存在是因为它在一个循环中,向模式添加了几个条件,例如*.a
and *.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
大概是因为通配符 in*.$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) 以这种方式使用数组应该扩展«到所有单独引用的元素»。如何使用变量执行此操作(参见我的第一次尝试)?让它发挥作用后,我回去尝试再次使用一个变量,用黑斜线单引号或\\'
,但没有任何效果(我刚得到bar
)。我必须做些什么来模拟“手动”,使用数组时完成的引用?
预先感谢您的帮助。
解决方案
必读:
a) 在最后一个使用数组的示例中,为什么需要在 ? 周围加上双引号
*.$i
?
您需要使用某种形式的引用来防止 shell 对*
. 变量未在单引号中展开,因此'*.$i'
不起作用。它确实抑制了全局扩展,但它也阻止了变量扩展。"*.$i"
抑制全局扩展但允许变量扩展,这是完美的。
要真正深入研究细节,您需要在这里做两件事:
- 转义或引用
*
以防止全局扩展。 - 视为
$i
变量扩展,但引用它以防止分词和全局扩展。
任何形式的引用都适用于第 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
而不是 glob。
当然。假设我们写:
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 手册页中,我们可以看到操作顺序:
- 大括号扩展
- 波浪号扩展、参数和变量扩展、算术扩展、命令替换和进程替换
- 分词
- 路径名(AKA glob)扩展
- 报价移除
让我们手动运行一下这些操作,看看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 不匹配任何东西。但它可以... †</sup>
报价移除
['find', '.', '(', '-name', 'bar', '-or', '-name', '"*.a"', ')']
反斜杠消失了。但是双引号仍然存在。
在前面的扩展之后,所有未引用的字符
\
,'
, 和"
不是由上述扩展之一产生的都将被删除。
这就是最终结果。双引号仍然存在,因此不是搜索命名的文件,而是*.a
搜索名称"*.a"
中带有文字双引号字符的文件。这种搜索注定会失败。
添加一对转义引号\"
根本没有达到我们想要的效果。引用并没有像他们应该的那样消失并破坏了搜索。不仅如此,它们也没有像应有的那样抑制 globbing。
TL;DR —变量内的引号与变量外的引号的解析方式不同。
†</sup> 前四个标记没有特殊字符。但最后一个"*.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 执行分词时,它也会扩展 glob。还记得我说过不带引号的变量受隐含的split+glob运算符的影响吗?这就是我的意思。拆分和通配是齐头并进的。
如果您写ls "foo*"
引号,请防止foo*
被拆分和通配。但是,如果您编写,ls $var
则将$var
被扩展、拆分和全局化。它没有被双引号包围。它包含双引号并不重要。当这些双引号出现时,为时已晚。已经执行了分词,因此也完成了通配。
推荐阅读
- python - When is self statement true and when is false?
- python - 如何在 Windows 中使用 python 2.7 将 .raw 文件转换为 .jpg 或 .png
- python - 关闭线程时套接字未关闭
- r - 在 Dygraph R 中调整图表区域
- react-native - Redux 状态更改不会更新循环内的组件
- python - Python 中的 Selenium - 下拉菜单
- vbscript - 根据日期显示图像 VB 脚本
- php - 如何为变量循环 Api
- c++ - 为什么我可以修改 const 引用返回?
- raspberry-pi - Gstreamer 无法在 Raspberry (Banana PI M2+) 上正确协商 v4l2src