在文件名采用 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模式匹配符号执行匹配文件名。*
是一个模式匹配多个字符,应匹配零个或多个字符的字符串。
$ 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
, fnmatch
return -1
, 表示匹配失败。ሒaa
将匹配像这样的有效字符。
在您的情况下,对于UTF-8
区域设置,\351
是无效字符,导致模式匹配失败。