find(1):如何实现星通配符使其在某些文件名上失败?

Mic*_*aël 32 shell character-encoding find filenames wildcards

在文件名为 UTF-8 的文件系统中,我有一个名称错误的文件;它显示为:D?sinstaller,实际名称根据 zsh: D$'\351'sinstaller,Latin1 for Désinstaller,本身是法语“卸载”的野蛮行为。Zsh 不会与它匹配,[[ $file =~ '^.*$' ]]但会与通配符匹配*——这是我期望的行为。

现在我仍然希望在运行时找到它find . -name '*'——事实上,我从不希望文件名在这个测试中失败。然而,随着LANG=en_US.utf8,该文件确实露面,我必须集LANG=C(或en_US,或'')为它工作。

问题: 背后的实现是什么,我怎么能预测到那个结果?

信息:Arch Linux 3.14.37-1-lts,查找(GNU findutils)4.4.2

dha*_*hag 26

这是一个非常好的捕获。通过快速查看 GNU find 的源代码,我会说这归结为fnmatch对无效字节序列(pred_name_commonin pred.c)的行为方式:

b = fnmatch (str, base, flags) == 0;
(...)
return b;
Run Code Online (Sandbox Code Playgroud)

此代码测试fnmatchfor 与 0 相等的返回值,但不检查错误;这会导致任何错误报告为“不匹配”。

许多年前,有人建议更改此 libc 函数的行为,使其始终在*模式上返回 true ,即使在损坏的文件名上也是如此,但据我所知,这个想法肯定已被拒绝(请参阅从https开始的线程://sourceware.org/ml/libc-hacker/2002-11/msg00071.html):

当 fnmatch 检测到无效的多字节字符时,它应该回退到单字节匹配,以便“*”有机会匹配这样的字符串。

为什么这更好或更正确?有没有现成的做法?

正如 Stéphane Chazelas 在评论中以及在 2002 年的同一个线程中所提到的,这与 shell 执行的 glob 扩展不一致,shell 不会阻塞无效字符。也许更令人费解的是,逆向测试只会匹配那些名称损坏的文件(在 bash 中创建文件touch $'D\351marrer' $'Touch\303\251' $'\346\227\245\346\234\254\350\252\236'):

$ find -name '*'
.
./Touché
./???

$ find -not -name '*'
./D?marrer
Run Code Online (Sandbox Code Playgroud)

因此,为了回答您的问题,您可以通过了解您fnmatch在这种情况下的行为以及了解如何find处理此函数的返回值来预测这一点;您可能无法仅通过阅读文档来发现。

  • @richard,这个想法是`D*staller` 会匹配`$'D\351sinstaller'`,就像它在我测试过的所有shell 中一样。鉴于 GNU fnmatch 行为与 GNU shell 的行为不一致,我认为这是一个错误。 (7认同)

cuo*_*glm 13

find -name选项使用 shell模式匹配符号来执行匹配文件名。*匹配多个字符的模式,应匹配零个或多个字符的字符串。

find使用fnmatch检查模式匹配,因此您可以使用ltrace检查结果:

$ touch $'\U1212'aa
$ touch D$'\351'sinstaller
$ LC_ALL=en_US.utf8 ltrace -e fnmatch find -name '*'          
find->fnmatch("foo", "foo", 0)                   = 0
find->fnmatch("Foo", "foo", 0)                   = 1
find->fnmatch("Foo", "foo", 16)                  = 0
find->fnmatch("*", ".", 0)                       = 0
.
find->fnmatch("*", "D\351sinstaller", 0)         = -1
find->fnmatch("*", "\341\210\222aa", 0)          = 0
./?aa
+++ exited (status 0) +++
Run Code Online (Sandbox Code Playgroud)

D\351sinstaller, fnmatchreturn -1, 表示匹配失败。?aa将匹配有效字符 like 。

在您的情况下,UTF-8区域设置\351是无效字符,导致模式匹配失败。

  • 至少,+1 用于使用 `ltrace`。我确实知道 `strace`,但 `ltrace` 对我来说是新的。迷人的! (3认同)