「フォルダの中身は確かにあるのに、ls や grep からは『そんなファイルはない』と言われる」——似た症状で検索して来た方向けに、原因と直し方をまとめておきます。
AIエージェント(Claude Codeなど)にMac上のフォルダを操作させているときに起きた問題です。
症状
半濁点(゜)を含む日本語フォルダ名を指定しても、コマンドから「見えない」と返される。
$ ls -la
drwxr-xr-x 3 me staff 96 9 10 08:55 サポート
$ ls -d *ポート*
ls: サポート: No such file or directory
親ディレクトリを ls すれば「サポート」というフォルダは確かに表示されている。なのに、同じ文字列を含めて指定した瞬間に「無い」と返ってくる。フォルダの中身も過去に扱ったこともあるのに、「フォルダが無い」「空に見える」ように扱われるため、存在しないと誤判定して処理をスキップしてしまう、という事故に遭遇しました。
前提の環境
- macOS(APFS)
- bash / zsh
- Python 3
直し方
1. ワイルドカードで半濁点をまたいで指定する
半濁点を含む文字のところだけ * で避ければ一致します。
$ ls -d *サ*ート*
サポート
grepで探す場合も同様に、半濁点の前後を分けて . や * を挟む。
2. プログラムからはUnicode正規化を揃えてから比較する
Pythonなら unicodedata.normalize で正規化形式を揃えてから比較・検索する。そのままでは動く実物として、以下はコピーしてすぐ試せます。
import unicodedata
import os
def find_dirs_matching(base_dir: str, keyword: str):
"""半濁点などを含むキーワードでも取りこぼさずに、
ディレクトリ名の部分一致を探す。"""
# ディスク上の実際の表現(NFD)に、比較する側の文字列も揃える
keyword_nfd = unicodedata.normalize("NFD", keyword)
matches = []
for name in os.listdir(base_dir):
path = os.path.join(base_dir, name)
if os.path.isdir(path):
name_nfd = unicodedata.normalize("NFD", name)
if keyword_nfd in name_nfd:
matches.append(name)
return matches
print(find_dirs_matching(".", "ポート"))
# => ['サポート']
keyword 側を書き換えた文字列(NFC)のまま比較すると一致しませんが、両方をNFDに揃えると一致します。
なぜそうなるのか
macOSのファイルシステム(APFS/HFS+)は、日本語などの濁点・半濁点付き文字をファイル名として保存するとき、内部的にUnicodeの正規化形式NFD(分解形)に近い独自の変種に変換して保存する仕様のようです。(Apple公式ドキュメントにも「variant of Normal Form D」と明記されている)
一部の記号範囲は例外的に分解されないものの、今回の濁点・半濁点はその例外に含まれておらず、以下の説明がそのまま当てはまる形です。
- NFC(合成形): 「ポ」を1文字(U+30DD)として認識
- NFD(分解形): 「ポ」を「ホ」(U+30DB)+半濁点の結合文字(U+309A)の2文字として認識
見た目はどちらも「ポ」で区別が付かないですが、内部のバイト列は別物。ターミナルやエディタで入力した文字列は多くの場合NFC(合成形)で、ディスク上の名前はNFD(分解形)なので、単純な文字列一致では食い違って「無い」ことになるということです。
まとめ
濁点・半濁点を含む日本語のファイル名・フォルダ名をmacOSで扱うときは、「入力した文字列」と「ディスク上の名前」が同じに見えても正規化形式が違いうることを前提にした方がよさそう。ワイルドカードで避けるか、比較の前に正規化を揃えるかのどちらかで、想定通りに一致するようになります。
もう1つ、根本的な回避策もありますね。
スクリプトやAIエージェントに自動で読ませる対象のフォルダ・ファイルは、濁点・半濁点を含まない名前(英数字など)にしておくと、この種の不一致自体はそもそも起きません。視認性のためにフォルダ名を日本語にすること自体は問題というわけではなく、自動処理の対象になるかどうかで使い分けるとよさそうです。