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处理此函数的返回值来预测这一点;您可能无法仅通过阅读文档来发现。
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是无效字符,导致模式匹配失败。