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

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

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

现在我仍然期望在运行时找到它find . -name '*'——事实上,我永远不会期望一个文件名会失败这个测试。但是,对于LANG=en_US.utf8,该文件确实不是出现,我必须设置LANG=C(或en_US,或'')才能使其工作。

问题: 背后的实现是什么?我如何预测结果?

信息:Arch Linux 3.14.37-1-lts、find (GNU findutils) 4.4.2

答案1

这真是一个很好的收获。快速浏览一下 GNU find 的源代码,我想说这可以归结为fnmatch无效字节序列(pred_name_common在 中pred.c)的行为方式:

b = fnmatch (str, base, flags) == 0;
(...)
return b;

此代码测试返回值fnmatch是否与 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

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

答案2

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

find用途匹配检查模式匹配,所以你可以使用跟踪检查结果:

$ 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) +++

D\351sinstaller, fnmatchreturn -1, 表示匹配失败。ሒaa将匹配像这样的有效字符。

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

相关内容