find(1):いくつかのファイル名で失敗するスターワイルドカードはどのように実装されますか?


31

ファイル名がUTF-8のファイルシステムでは、ファイル名に誤りがあります。それはD�sinstaller、として表示されます。zsh:による実際の名前 D$'\351'sinstaller、Latin1はDésinstaller、それ自体は「アンインストール」のフランス語の野barです。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
convmvファイル名をutf-8に変換することを検討しましたか?
ctrl-alt-delor

@richard:実際、私はファイル名の[[ $file =~ '^.*$' ]]使用の失敗に頼ってrecodeいますがconvmv、必要に応じて調べます。ありがとう。
ミカエル

回答:


25

それは本当に素晴らしいキャッチです。GNUの検索のためのソースコードを簡単に見てから、私はこれがどのように集約言うfnmatch無効なバイトシーケンスに振る舞う(pred_name_commonpred.c):

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

このコードは、戻り値がfnmatch0であるかどうかをテストしますが、エラーをチェックしません。これにより、エラーは「一致しない」として報告されます。

何年も前に、このlibc関数の動作を変更し*て、壊れたファイル名でもパターンで常にtrueを返すことが提案されてきましたが、私が知ることができることから、アイデアは拒否されたに違いありません(httpsで始まるスレッドを参照してください) ://sourceware.org/ml/libc-hacker/2002-11/msg00071.html):

fnmatchが無効なマルチバイト文字を検出した場合、「*」がそのような文字列と一致する可能性があるように、単一バイト一致にフォールバックする必要があります。

そして、なぜこれがより良いまたはより正しいのですか?既存のプラクティスはありますか?

StéphaneChazelasのコメントおよび2002年の同じスレッドで述べられているように、これは無効な文字でチョークしないシェルによって実行されるグロブ拡張と矛盾しています。おそらくもっと困惑するのは、テストを逆にすると、壊れた名前を持つファイルのみに一致するという事実です(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'。GNU fnmatchの動作がGNUシェルの動作と一致していないことを考えると、これはバグだと思います。
ステファンシャゼル

1
徹底的な答え、dhag。とても有難い。fnmatchが準拠する標準仕様を指摘していただけますか?.エンコーディング内の有効な文字のみに一致するように指定する通常のPOSIX正規表現仕様を見つけることができます。そのため、.*無効な文字列に一致しないという期待がありますが、グロビングスターの一致する仕様は見つかりません。
ミカエル

1
オンラインで見つけることができる最も近い仕様は、このOpenGroupページにあります。それは述べてマッチングが文字を符号化するために使用されるビットパターンではなく、文字のグラフィック表現に基づくものでなければなりません。また、<アスタリスク>は、ヌル文字列を含む任意の文字列に一致するパターンです。 これは間違いなく@StéphaneChazelasの提案と解釈できます。13年後、再びアップストリームのpingを実行する時が来るかもしれません:
ミカエル

@Michaël、これ以上良いものは見つかりませんでした。おそらく、比較のポイントとして、Mac OSでのGNU findはシェルのグロビングと一貫した方法で動作し-name '*'ます(つまり、すべてのファイルに一致し、壊れた名前が含まれます)fnmatch。 GNUバージョンとは異なり、無効な文字に対して行われるべきことの解釈が、おそらく間違いなくより安全です。
dhag

13

find -nameオプションは、シェルパターンマッチング表記を使用して、ファイル名のマッチングを実行します。*複数の文字一致するパターンであり、0個以上の文字の文字列に一致します。

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\351sinstallerfnmatchリターン-1、それが一致しなかったことが示されました。のような有効な文字ሒaaが一致します。

あなたの場合、UTF-8ロケールで\351は、無効な文字であるため、パターンマッチングが失敗します。


3
少なくとも、の使用に対して+1 ltrace。私は知ってstraceいましたltraceが、私には新しいです。ラブリー!
ミカエル
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.