Shell 脚本、查找 -name 和通配符扩展

Ste*_*fan 4 shell find quoting

我正在尝试find -namesh脚本中使用先前为条件计算的复杂参数。简化,就像

cond="-name '*.txt*"
find . $cond -ls
Run Code Online (Sandbox Code Playgroud)

但是现在我遇到的问题$cond是,在调用之前,shell 扩展了in 的通配符,find或者不扩展了find.

为了测试,在一个空的测试目录中我做了:

touch a.txt b.txt c.dat
Run Code Online (Sandbox Code Playgroud)

然后迭代

cond="-name '*.txt'"; echo $cond; find . $cond
Run Code Online (Sandbox Code Playgroud)

使用不同的值$cond,使用我能想到的所有类型的引用/转义。

要么我得到所有四个条目(包括.目录)或没有文件——或者find抱怨b.txt是一个未知的主或操作员。当然,正确的输出应该只是两个文本文件。

任何提示?我确定我只是缺少一些基本的东西。

Sté*_*las 6

在类似 Bourne 的 shell 中(除了zsh),在列表上下文中不加引号的变量扩展是split+glob运算符。在:

cond="-name '*.txt'"; echo $cond
Run Code Online (Sandbox Code Playgroud)

的内容$cond首先根据$IFS特殊变量的值进行拆分。默认情况下,这是在 ASCII 空格、制表符和换行符上。

所以这是分裂-name'*.txt'。然后应用glob部分。对于包含通配符的每个单词(此处*为第二个),该单词将扩展为匹配该模式的文件列表,或者如果没有匹配的文件则保持不变。因此,'*.txt'将扩大到文件的当前目录中其名称以列表'和结束.txt',或者'*.txt'如果没有文件匹配。

然后将该单词列表作为单独的参数传递给echo。如果没有匹配的文件,这意味着echo将收到 3 个参数:"echo","-name""'*.txt'".

当然,这同样适用于find命令。

您希望find命令接收参数-nameand *.txt,因此您需要调整split+glob运算符。

您需要 的值$IFS来匹配您在 中使用的分隔符$cond。所以例如:

cond='-name:*.txt'
IFS=':'
Run Code Online (Sandbox Code Playgroud)

并且您不想要split+glob运算符的glob部分,因此您需要使用以下命令禁用它:

set -f
Run Code Online (Sandbox Code Playgroud)

所以就变成了:

cond='-name:*.txt'
IFS=:; set -f
find . $cond -ls
Run Code Online (Sandbox Code Playgroud)

或者你可以这样做:

cond='-name *.txt'
set -f
find . $cond -ls
Run Code Online (Sandbox Code Playgroud)

并假设$IFS尚未修改其默认值。

或者,如果您的 shell 支持数组,您可能希望使用它而不是依赖标量变量并期望 shell 在扩展时将其拆分。

这适用于ksh93, mksh,bashzsh

cond=(-name '*.txt') # an array with two elements: "-name" and "*.txt"
find . "${cond[@]}" -ls # elements of the array expanded as separate arguments
                        # to find.
Run Code Online (Sandbox Code Playgroud)

  • @Stefan,请注意“set -f”完全禁用通配符(不仅仅是在变量扩展或命令替换时完成的隐式通配符)。这意味着“rm -- *.txt”将不再起作用。您可能还需要考虑更好的 shell,它们不会在变量扩展时执行通配符,例如 `zsh`、`rc` 或 `fish`(其中,`zsh` 最像 _Bourne_)。 (2认同)