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


31

在文件名使用UTF-8的文件系统中,我的文件名称错误;它显示为:D�sinstaller,根据zsh:的实际名称 D$'\351'sinstaller,Latin1表示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


1
您是否考虑convmv过将文件名转换为utf-8?
ctrl-alt-delor

@richard:实际上,我过去常常依赖于[[ $file =~ '^.*$' ]]无法使用recode文件名,但是现在我将调查convmv是否需要。谢谢。
迈克尔

Answers:


25

这是一个非常好的收获。快速浏览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éphaneChazelas在评论中以及同一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处理此函数的返回值来进行预测。您可能无法仅通过阅读文档来查找。


我对为什么没有解决办法的猜测*是,那将与不一致D*staller
ctrl-alt-delor

7
@richard,这个想法将D*staller$'D\351sinstaller'我测试过的所有shell的全局匹配。鉴于GNU fnmatch行为与GNU Shell行为不一致,我会说这是一个错误。
斯特凡Chazelas

1
dhag,很深入的回答;非常感激。您介意指出fnmatch符合的标准规格吗?我可以找到通常的POSIX正则表达式规范,该规范指定.只应与编码中的有效字符匹配(因此,我的期望.*与无效字符串不匹配),但是我找不到与星号匹配的规范。
迈克尔

1
我在网上可以找到的最接近的规格在此OpenGroup页面上。它指出匹配应基于用于编码字符的位模式,而不是基于字符的图形表示。该<星号>是应匹配任何字符串,包括空字符串的图案。 这可以说是@StéphaneChazelas的建议。13年后,它可能是时间再次执行ping上游:-)
迈克尔

@Michaël,我也找不到更好的东西。作为比较,也许在Mac OS上,GNU find的行为与shell的状态一致(即,-name '*'匹配所有文件,包括损坏的名称),因此大概是BSD的fnmatch,它不要求POSIX.2符合性,与GNU版本不同,它对无效字符应该做什么的解释有所不同,并且可以说更精明。
dhag 2015年

13

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

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

使用D\351sinstallerfnmatchreturn -1,表示它不匹配。像这样的有效字符ሒaa将被匹配。

在您的情况下,对于UTF-8语言环境,\351是无效字符,导致模式匹配失败。


3
至少要使用+1 ltrace。我确实知道strace,但是ltrace对我来说是新的。可爱!
迈克尔
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.