查找命令:排除特定文件夹中的文件模式

use*_*235 4 find

我希望该find命令排除一个文件夹中的特定文件模式,但不排除该文件夹的子文件夹。例如,如果我想排除subdir1/subdir1.1/UndesiredFiles*.tgz,则以下内容将不起作用,因为星号与包括文件夹名称分隔符在内的连续字符匹配/:

find * -not -path 'subdir1/subdir1.1/UndesiredFiles*.tgz'
Run Code Online (Sandbox Code Playgroud)

上面排除了以下内容,我不想排除这些内容:

subdir1/subdir1.1/UndesiredFilesAndMore/*.tgz
subdir1/subdir1.1/UndesiredFilesAndMore/StillMore/*.tgz
Run Code Online (Sandbox Code Playgroud)

我使用的是 Gnufind版本 4.9.0。

Sté*_*las 6

某些find实现支持-regex与 相同的谓词,-path只是它使用正则表达式(尽管其变体随实现和选项或其他谓词而变化)而不是 shell glob 模式。鉴于您find支持非标准 BSD 风格-not谓词,它很可能就是其中之一。

LC_ALL=C find . ! -regex '\./subdir1/subdir1\.1/UndesiredFiles[^/]*\.tgz'
Run Code Online (Sandbox Code Playgroud)

我们将*glob 运算符(与 regexp 相同.*:0 个或更多字符)替换为 regexp [^/*](除 0 个或更多字符/)。

正则表达式默认是锚定的,我们不需要显式的^or $。

正如.匹配任何字符的正则表达式运算符一样,我们需要使用\.(尽管[.]也可以)对其进行转义,以使其仅匹配文字.(这一点很容易被忽视,因为.s 在文件名中很常见)。

通常LC_ALL=C需要,除非您可以保证所有文件和目录名称仅由用户区域设置中的有效字符组成(这也适用于您的顺便说-path一句)。

在 BSD 上,-regex采用标准基本正则表达式,可以使用选项-E(如 forgrep或sed)将其更改为标准扩展正则表达式。对于 GNU 来说find,默认情况下,这是来自 emacs 的古老版本的正则表达式,但可以使用谓词更改-regextype为各种其他风格。无论如何,上面的特定正则表达式适用于任何变体。


对于find不支持 的 s -regex,您可以执行以下操作:

LC_ALL=C find . ! '(' -path './subdir1/subdir1.1/UndesiredFiles*.tgz' \
  ! -path './subdir1/subdir1.1/*/*' ')'
Run Code Online (Sandbox Code Playgroud)

即过滤掉./subdir1/subdir1.1/UndesiredFiles*.tgz除了那些*匹配的至少包含一个的/。

或者你可以perl进行过滤:

find . -exec printf '%s\0' {} + |
  perl -l -0ne 'print unless m{^\Q./subdir1/subdir1.1/UndesiredFiles\E[^/]*\.tgz\z}'
Run Code Online (Sandbox Code Playgroud)

在那里,我们可以将\Q...\E里面的内容视为固定字符串,从而无需转义任何正则表达式运算符。在这里,我们确实需要^在开头和\z结尾处锚定正则表达式($在 perl 中不匹配结尾处或结尾换行符之前的正则表达式,因此会“错误地”排除文件$'UndesiredFiles.tgz\n')。

(替换print为system "cmd", $_以运行以路径作为参数的命令)。

一些(大多数)find实现-exec printf '%s\0' {} +可以替换为-print0. 某些实现通过or选项xargs支持该输出格式:-0-d '\0'

find . -print0 |
  perl -0 -lne 'print unless m{^\Q./subdir1/subdir1\.1/UndesiredFiles\E[^/]*\.tgz\z}' |
  xargs -r0 cmd
Run Code Online (Sandbox Code Playgroud)

-l选项移到后面, -0以便输出记录分隔符也为 NUL。


如果使用zshshell,则不需要find,您可以执行以下操作:

set -o extendedglob
print -rC1 -- **/*~subdir1/subdir1.1/UndesiredFiles[^/]#.tgz(ND)
Run Code Online (Sandbox Code Playgroud)

其中#regex 的扩展glob 等价物是except / and-not*运算符,并且应用于(如果没有匹配则展开为空)和(包括隐藏文件)到该一个 glob 扩展以 match的行为。您还可以将限定符添加到列表中,以进一步匹配 match的行为。~(ND)nullglobdotglobfindoNNofind

print -rC1 -- printrs 列上的列表1 C,但您当然可以使用另一个命令或在循环中循环列表for。

**/*(匹配任意数量的子目录中任意名称的文件,简称)如果设置该选项,则(*/)#*可以缩写为。**globstarshort


无论如何,请注意,虽然所有这些都排除./subdir1/subdir1.1/UndesiredFiles-whatever.tgz,但它们不会排除./subdir1/subdir1.1/UndesiredFiles-whatever.tgz/other/file. 您需要调整模式或使用谓词-prune find来排除它们。