日本語ファイル名問題

注意

この記事は古い記事です。

過去に東京大学教育用計算機システム(ECCS)で発生していた問題について説明しています。問題としてはECCS以外の一般の環境でも発生しうる問題であり、ECCS以外でも有用であると考え残してありますが、内容は更新されていません。また、一部の事項は、東京大学教育用計算機システム(ECCS)に固有のものであり、一般の環境には該当しなかったり、適用できなかったりする部分がございますことを、予めご了承ください。

現在のECCSではこの問題は対策されており、以下に記述のある問題のほとんどは解消されています。しかしながら、最近においてもFinderで若干の不具合が残っているという報告があります。
相談員ミーティング(2018年6月15日)
濁点・半濁点を名前に含むフォルダのFinder等における不具合(ECCS広報)

目次

概要 「日本語ファイル名問題」とは?

「アーカイ.zip」や「レート.docx」のように、ファイル名に特定の文字(例 濁点・半濁点付きのかな)(以降では、この文字を「問題となっている文字」と表記することがあります)が含まれていると、Macintosh 環境の Finder には表示されているのに、そのファイルをアプリケーションソフトウェアで開けなかったり、ゴミ箱を空にしようとしてもそのファイルを削除できなかったりするなどの不具合が発生します。

ファイル名に濁点・半濁点付きのかなが含まれている場合にこのような症状が発生することが多く見られたことから、ここでは通称を「日本語ファイル名問題」としていますが、ここで「問題となっている文字」は日本語の文字に限定されません。技術的な詳細については、解説をご覧ください。

過去の質問・回答

ここでは、以前に教育用計算機システム(ECCS)で発生したことがあるが、現在では発生しない問題を取り上げています。教育用計算機システム(ECCS)以外の環境では現在も発生する可能性がありますので、参考情報としてここに残してあります。

質問・回答 1

質問

ファイルをアップロードしようとするとエラーになります。

回答

ファイル名に対して正規化形式 C(NFC、合成。分解済み文字を合成します)で Unicode 正規化するか、問題となっている文字を削除してください。

ファイル名に含まれる分解済み文字を合成するために、以下の Ruby スクリプトをご用意しました(Unicode 正規化はしません[註 1])。
/mnt/ECC/bin/tonfc.rb

#!/opt/local/bin/ruby1.9 --
#coding:us-ascii
ARGV.each{|_s|
s=_s.dup
s.force_encoding('UTF-8-MAC')
File.rename(s,s.encode('UTF-8'))
}

質問・回答 2

質問

ファイルを Macintosh 環境の Finder で圧縮すると「アーカイブ.zip」というファイル名になってしまい、開けません。

回答

  • 原因:2つ以上のファイル・フォルダを同時に選んで、右クリックメニューから「n 項目を圧縮」(「n」には、実際には選択したファイルの個数が表示されます)を選ぶと、ファイル「アーカイブ.zip」が作成されますが、これは解凍(展開)できません(USB メモリーなどに作成した「アーカイブ.zip」は解凍でき、その場合には問題ございません)。
  • 回避方法:一旦新規フォルダー(1 つだけ)を作成し、そこに圧縮したいファイルをすべて格納します。そして、このフォルダーを(右クリックメニューで指定して)圧縮します。
  • 誤ってファイル「アーカイブ.zip」を作成してしまった場合には、下の質問・回答 3 に進んでください。

質問・回答 3

質問

  • ダウンロードしたファイルが開けません。
  • (Firefox)ダウンロードに失敗し、~.partというファイルが残っています。
  • Wordの印刷がうまくいかなくてpdfに出力しましたが、そのpdfを開くことができなければゴミ箱に入れることもできません。
    • それどころか、OS X のターミナルに「rm」(ファイル削除コマンド)と入れてから、ファイルのアイコンをターミナルのウィンドウにドラッグアンドドロップ(DnD)して Return キーを押しましたが、ファイルが見つからないと出ました。
  • (質問・回答 2 のように)ファイル「アーカイブ.zip」を作成してしまいました。

回答

  • https://www.ecc.u-tokyo.ac.jp/announcement/2013/04/24_1724.html に従って対処してください。
  • また、ファイル名に含まれる合成済み文字を分解するために、以下の Ruby スクリプトをご用意しました(Unicode 正規化はしません[註 1])。
    /mnt/ECC/bin/tonfd.rb
    #!/opt/local/bin/ruby1.9 --
    #coding:us-ascii
    ARGV.each{|_s|
    s=_s.dup
    #s.force_encoding('UTF-8')
    #File.rename(s,s.encode('UTF-8-MAC'))
    s.force_encoding('UTF-8-MAC')
    File.rename(s.encode('UTF-8'),s)
    }

質問・回答 4

質問

Macintosh 環境で、Finder を使用してもゴミ箱を空にできません。または、Finder でゴミ箱を空にする操作を行っても、ゴミ箱に一部のファイルが残ってしまい、何回同じ操作を行ってもそのファイルをゴミ箱から削除できません。

回答

FAQ「ゴミ箱を空にできない」をご覧ください。

解説

注意 この記事では一部で字形について説明していますが、互換性を考慮して、直接使用する文字は Shift_JIS で定義されているものに限定し、それで直接表現できない場合には、遠回しに表現するものとします(例 「丸付き数字の『1』」)。

ファイル名が合っているはずなのに、ファイルを見つけられなかったり、ファイルを開けなかったりする理由は、簡単には、文字の中には同じ意味でも表現の異なるものがあり、ファイル名が合っているように見えて、実は表現が異なっているために、ファイル名が異なるものとみなされていることにあります。それには大変深い事情があるのです…。

同じ意味でも表現の異なる文字

コンピューターで文字列(複数の文字が一連につながっているもの)を処理する場合に、通常は文字の形状そのものではなく、各文字に与えられている番号である文字符号(文字コード)で処理します。この文字符号の与え方は文字集合(文字セット)(例 JIS X 0208「7 ビット及び 8 ビットの 2 バイト情報交換用符号化漢字集合」(旧: JIS C 6226)、Unicode)によって様々です。また、文字符号の表現方法(文字符号化方式、エンコーディング)(例 Unicode に対しては UTF-8、UTF-16LE など)も様々です。

  • 例 「あ」([HIRAGANA LETTER A])の文字符号は、JIS X 0208 では「4 区 2 点」、Unicode では「U+3042」(Unicode ではこのように十六進表記するのが普通であり、これを十進表記すると「12354」になります)です。

ところで、文字集合で定義され、文字符号が与えられている文字のうち、同じ文字(必ずしも同じ形でなければならないことはありません。直感的には「意味上同じと見なせる」と考えるのが簡単でしょう)でありながら、複数の文字符号が与えられているものがあります。

  • その代表例はダイアクリティカルマーク(diacritics)付きの文字です。ダイアクリティカルマークは他の文字に付加され、発音などを若干変化させる効果を持ちます。ドイツ語などでよく用いられる、文字の上に 2 個の点を水平に付けるウムラウトは、ダイアクリティカルマークの代表例の一つです。ところで、ダイアクリティカルマーク付きの文字を表現するには、最初からダイアクリティカルマークが合成された文字として表現する方法と、基本となる文字とダイアクリティカルマークを分離して表現する方法とがあります。
    • 例 ウムラウト付きの「U」([LATIN CAPITAL LETTER U]+[COMBINING DIAERESIS]、または[LATIN CAPITAL LETTER U WITH DIAERESIS])の文字符号は、Unicode では「U+0055 U+0308」および「U+00DC」であり、「U+0055 U+0308」、「U+00DC」のいずれを使用しても、同じ文字が表示されることが期待されます[註 2]。この場合において、[U+0308 COMBINING DIAERESIS]はここではダイアクリティカルマークに分類され、その直前にある[U+0055 LATIN CAPITAL LETTER U]を修飾し、発音などを若干変化させます。一方、[U+00DC LATIN CAPITAL LETTER U WITH DIAERESIS]はダイアクリティカルマークがすでに合成されている文字です。
  • ダイアクリティカルマークは、日本語では、濁点と半濁点が該当しましょう(ただし、日本で日本語の濁点や半濁点がダイアクリティカルマークと言われることはほとんどありません)。先ほどと同様に、濁点と半濁点を合成した文字で表現する方法と、分離して表現する方法とがあります。
    • 例 「が」([HIRAGANA LETTER KA]+[COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK]、または[HIRAGANA LETTER GA])の文字符号は、Unicode では「U+304B U+3099」および「U+304C」であり、「U+304B U+3099」、「U+304C」のいずれを使用しても、同じ文字が表示されることが期待されます[註 2]。この場合において、[U+3099 COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK]はここではダイアクリティカルマークに相当し、その直前にある[U+304B HIRAGANA LETTER KA]を修飾し、発音などを若干変化させます。一方、[U+304C HIRAGANA LETTER GA]はダイアクリティカルマークがすでに合成されている文字です。
    • 注意 [U+3099 COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK]と[U+309B KATAKANA-HIRAGANA VOICED SOUND MARK]はいずれも濁点で、[U+309A COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK]と[U+309C KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK]はいずれも半濁点ですが、文字の名前に「COMBINING」が付いている方は結合文字(前の文字を修飾する文字)、付いていない方は独立した文字という違いがあります。
  • 別の同じ意味を持つ文字として、丸付き数字を、合成済み文字で表現したものと、分解済み文字(合成していない文字)で表現したものとがあります。
    • 例 丸付き数字の「1」の文字符号は、JIS X 0213「7 ビット及び 8 ビットの 2 バイト情報交換用符号化拡張漢字集合」では「1 面 13 区 1 点」、Unicode では[U+0031 DIGIT ONE]+[U+20DD COMBINING ENCLOSING CIRCLE]、および、[U+2460 CIRCLED DIGIT ONE]です。ここで、Unicode の[U+20DD COMBINING ENCLOSING CIRCLE]は結合文字であり、その直前の[U+0031 DIGIT ONE]を修飾します。JIS X 0208「7 ビット及び 8 ビットの 2 バイト情報交換用符号化漢字集合」では、丸付き数字の「1」は定義されていません[註 3]
  • やはり互いに同じ意味を持つ文字として、半角文字・全角文字があります。これらは文字の形こそ厳密には異なりますが、抽象的には同じ意味を持つ文字とみなせます。実際、半角文字で書かれた英数字が全角文字に置換されても(例 「15 個」→「15個」)、体裁こそ変化します[註 4]が、意味が変化することはありません。
    • 例 「0」([DIGIT ZERO])の文字符号は、JIS X 0201「7 ビット及び 8 ビットの情報交換用符号化文字集合」(旧: JIS C 6220)では十進で「48」(十六進で「30」)、JIS X 0208「7 ビット及び 8 ビットの 2 バイト情報交換用符号化漢字集合」では「3 区 16 点」、Unicode では「U+0030」です。一方、俗に言う全角数字の「0」(「0」、[FULLWIDTH DIGIT ZERO])の文字符号が別途与えられていることがあり、Unicode では「U+FF10」です[註 5]
  • 他に互いに同じ意味を持つ文字として、異体字があります。これらも文字の形こそ厳密には異なりますが、抽象的には原則として同じ意味を持つ文字と見なせます。固有名詞でない限りは、どの異体字を使用しても、意味が変化することはありません。一方、固有名詞では、多くの場合に特定の字体が指定されます(他の字体を使用することが好まれません)ので、用途によっては異体字は異なる意味を持つとみなせるかもしれません。
    • 例 「」(くず)は植物の名前ですが、これにはいくつかの異体字があり、代表的なものには、地名「東京都葛飾区」で使用されている文字「葛」(通称: 人かつ)と、地名「奈良県葛城市」で使用されている文字「葛」(通称: ヒかつ)があります。植物の種類の名前として「葛」を使用した場合には、どの字体を使用しても同じ種類の植物を指します。しかし、地名「東京都葛飾区」と「奈良県葛城市」では、それぞれ正式な字体が指定されており(東京都葛飾区奈良県葛城市)、正式な字体を使用することが推奨されています[註 6]

同じ文字なのに複数の文字符号が与えられているのは奇妙に感じられるかもしれませんが、様々な歴史的な事情があります(例 註 5)。しかし、これが、複数の文字列を比較して同じかどうかを判定するときに厄介な問題となり、例えば、指定されたファイルを探し出せなかったり、文章から指定された文字列を見つけられなかったりする原因になります。

  • 例 「レポート.txt」という名前のファイルを開きたいとします。ディスクのファイルシステム上では、ファイル名の「ポ」の文字符号が Unicode で[U+304D KATAKANA LETTER PO]のように合成済み文字になっているとします。これに対し、ファイル名を探すための文字列で「ポ」の文字符号を Unicode で[U+30DB KATAKANA LETTER HO]+[U+309A COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK]のように分解済み文字(合成していない文字)としてしまうと、先ほどの合成済み文字と同じ意味を持ち、同じように表示されることが期待されながら、文字符号が異なるために、(後述の Unicode 正規化のような対策を施さない限り)異なるファイル名と見なされ、ファイルを探し出せないことになります。
  • 例 文書の中の「3 丁目」という部分を検索し、「4 丁目」に置換したいとします。ここで、元の文字列として指定したもののうち、「3」の文字符号を Unicode で[U+0033 DIGIT THREE]とします。しかし、文書の中では「3丁目」のように数字が全角文字で使用されている場合、その「3」の文字符号は[U+FF13 FULLWIDTH DIGIT THREE]になります。人間は「3 丁目」([U+0033 DIGIT THREE]が使用されている方)も「3丁目」([U+FF13 FULLWIDTH DIGIT THREE]が使用されている方)も同じように読めます。しかし、コンピューターでは文字符号が異なりますので、(後述の Unicode 正規化のような対策を施さない限り)異なる文字列と見なされ、置換の対象になりません(もし、半角数字と全角数字が混在していると、置換の結果、「4 丁目」と「3丁目」が混在することになりますが、これでは困ります)。

Unicode における文字の考え方

Unicode は様々な国・言語の文字を収録し、それぞれの国・言語の規則に従って文字を正しく表現することを目標にしており、現在では様々なシステムに採用されています。ファイル名も、現在では Unicode で処理・保存されることが多くなりました。

Unicode における文字は基本的に抽象的な意味を重視し、字形を規定するものではありません(最近ではこの原則が崩れていますが…)。字形が完全には一致していなくても、わずかに異なるだけであれば、抽象的な意味さえ同じであれば、統合されるのが理想です。このような統合を包摂と言います。包摂(統合)を行わないと、字形がわずかでも異なるたびに新しい文字として定義・収録しなければなりません。それでは文字集合の管理が非常に困難になりますし、文字列の検索でも非常に不便です(「葛飾区」が、明朝体で印刷されている場合と角ゴシック体で印刷されている場合、さらには、A さんによって書かれた場合と B さんによって書かれた場合とで、字形が異なることを理由に互いに異なる場所を指していると思ったら、大変なことになります)。

  • 例 上の例で取り上げたダイアクリティカルマーク(日本語の濁点・半濁点を含みます)については、最初から合成せずに分解された状態で管理しておけば、様々な文字符号の組み合わせによって、様々な「文字」を表現することができます。これに従うのなら、ウムラウト付きの「U」として有効な文字符号は「U+0055 U+0308」のみであり、「U+304C」は有効なものとして認められないはずでした。実際、今後追加される文字は基本的に分解済みの文字です[註 7]
  • 例 上の例で取り上げた半角文字・全角文字もどちらか一方で十分です。どうしても半角文字・全角文字を切り替えたいのであれば、ワードプロセッサーや組版システムで字形の指定をすればよいのです[註 8]。これに従うのなら、数字の「0」として有効な文字符号は「U+0030」のみであり、「U+FF10」は有効なものとして認められないはずでした。
  • 例 上の例で取り上げた「葛」も、基本的には異体字を考慮する必要がありません。固有名詞である「東京都葛飾区」や「奈良県葛城市」を表現する上でどうしても字体を厳密に指定したいのであれば、本来はワードプロセッサーや組版システムの機能を使用すればよいはずです。
  • 例 漢字は中国語、日本語、韓国語とでしばしば異なる字形を持ちますが、これも基本的にはいずれか一つで十分です(この結果出来上がったのが CJK 統合漢字(CJK Unified Ideographs)です[註 9])。特定の国・言語として正しい字形を取得したいのであれば、フォントを指定するか、ワードプロセッサーや組版システムの機能を使用すればよいはずです。

一方で、異なる意味を持つもの、政治的な事情があるものは、包摂(統合)の対象にはなりません。

  • 例 英大文字「A」に関連する文字符号で、[U+0041 LATIN CAPITAL LETTER A](通常の文字)、[U+1D400 MATHEMATICAL BOLD CAPITAL A](数式用の太字)、[U+1D434 MATHEMATICAL ITALIC CAPITAL A](数式用のイタリック文字)などは、互いに異なる文字と見なされます。例えば、数式用の太字と数式用のイタリック文字は互いに異なる意味を持ち、例えば、太字はベクトルや行列、イタリック文字はスカラー(実数や複素数などの単なる数値)を表します。
  • 例 固有名詞で特定の字形を指定するために使用される異体字選択子がある場合には、包摂(統合)しません。例えば、「東京都葛飾区」の「葛」と「奈良県葛城市」の「葛」は互いに異なる字形ですが、何の対策も施さないと、包摂(統合)の対象になり、互いに同じ字形で表示される可能性が非常に高くなります。異体字選択子で字形を指定すれば、包摂(統合)の対象から外され、正式な字形が維持されます。
  • 例 日本の携帯電話(スマートフォンを含む)で頻用されている絵文字(Emoji)は、文字でありながら絵でもあり、通常の文字と同様に文字符号が与えられている一方で、通常の絵のように様々な色を使用することができます(通常の文字では色は 1 色のみであり、しかも外部からの指定に従って着色されます)。絵文字は Unicode ではバージョン 6.0 以降で定義されています。日本の携帯電話の絵文字には人間の顔を模擬したものが多数あり、Unicode にも収録されていますが、「肌の色が特定の人種に偏向している」という批判を受けたため、Unicode 8.0 では肌の色を指定するための結合文字が追加される予定です。もし、肌の色が変化しても意味が変化するわけではないとして、包摂(統合)の対象にしたり、肌の色を正しく反映しなかったりすると、人種問題に発展し、大変危険な状態に陥る可能性があることが懸念されています。そのため、単なる異体字とは異なり、肌の色を指定するための結合文字を基底文字(base characters)に正しく反映させることが強く推奨されています。

その一方で、Unicode は互換性も重視しています。Unicode は Shift_JIS などの従来の文字集合・文字符号化形式からの乗り換えを容易にし、真に世界共通の文字集合として利用されるようにするために、Unicode としては包摂(統合)の対象になる文字であっても、従来の文字集合で異なる文字として定義されている場合には、異なる文字として収録し、従来の文字集合との間で一対一対応を結べるようにしました。これにより、従来のシステムで作成された、従来の文字集合・文字符号化形式に基づくデータを Unicode で処理しても、異なる文字が同一視されるという情報落ちを防ぐことができます。ここで、本来の Unicode の基準に従っている文字を正準文字(canonical characters)(「canonical」には「好ましい」(preferred)の意が含まれます)、単に互換性のために収録された文字を互換文字(compatibility characters)と言います。

  • 例 半角文字・全角文字については、大雑把には、英数字では半角文字(より正確には、U+0020〜U+007F の範囲内にあるもの)が正準文字、全角文字(より正確には、U+3099〜U+30FF の範囲内にあるもの)が互換文字です。カタカナでは全角文字が正準文字、半角文字(より正確には、U+FF61〜U+FF8F の範囲内にあるもの)が互換文字です。
  • 例 「神」には異体字がいくつかありますが、これについては、(後述する異体字選択子を使用しない場合には)正準文字である CJK 統合漢字である「U+795E」(文字の左半分が「ネ」になっています)と、互換文字である CJK 互換漢字(CJK Compatibility Ideographs)「U+FA19」(文字の左半分が「示」になっています)が収録されています。

なお、互換文字として収録されるのは、あくまで一つの文字集合で別々に定義されている文字の場合であって、例えば、異なる国・言語で異なる字形を持っているというだけでは、互換文字としては収録されません。

互換文字は、正準文字と比較するとより好ましくはないのですが、使用を禁止されているわけでもなければ、廃止予定(deprecated)でもありません。日常の文書作成などに互換文字を使用することができます。

解決策: Unicode 正規化

しかし、このように文字に対して複数の表現を認めてしまうことにより、何らかの対策を施さないと、ファイルを名前で探し当てる場合(例えば、「レポート.txt」という名前のファイルがあるのに、「レポート.txt」を指定しても開けません。これは文字「ポ」のせいです)など、文字列の検索は不便になります。

そこで、このような文字列の表現を統一し、互いに比較できる(同じ意味のものは同じと判定できる)ようにする方法として、Unicode には正規化(normalization)というものが用意されています。Unicode 正規化では、文字を分解・統一し(decompose characters)、結合文字を並べ替えます(reorder combining marks)。合成の指定があれば、そのあとにさらに文字を合成します(compose characters)。

文字を分解・統一して並べ替えて終了する正規化形式には正規化形式 D(Normalization Form Canonical Decomposition、NFD)や正規化形式 KD(Normalization Form Compatibility Decomposition、NFKD)があり、その後に文字を合成する過程を含む正規化形式には正規化形式 C(Normalization Form Canonical Composition、NFC)や正規化形式 KC(Normalization Form Compatibility Composition、NFKC)があります。

  • 「C」は合成(compose)、「D」は分解(decompose)を意味します。「C」では最後に文字の合成を行いますが、「D」では行いません。
  • 「K」は互換(compatibility)を意味します(これがないものは正準(canonical)です)。正準のものは字形の変更を伴いませんが、互換のものは字形の変更を伴います(例えば、全角英数字や上付き英数字などが半角英数字に置換されます)。字形の変更を伴う正規化形式 KC や正規化形式 KD は、同じ意味を持つ文字列の検索や、意味を基にした文字種判定にしばしば有用ですが、乱用すると、本来互いに異なる意味で使用されている可能性のある文字が互いに同じ文字に化けてしまい、区別が付かなくなることになります。この問題に対処するには、必要があれば、ワードプロセッサーや組版システムの機能で書式指定することになります。
    • 既に述べたように、Unicode は字形を規定しないことを原則としていますが、互換文字ではしばしば名前に「全角」(fullwidth)、「半角」(halfwidth)、「上付き」(superscript)、「下付き」(subscript)といった、字形をある程度意味する修飾語が付いていますので、「互換」な正規化(正規化形式 KC や正規化形式 KD)が規定されているのです。一方、CJK 互換漢字が CJK 統合漢字に置換される(例 U+FA19 → U+795E(「神」))のは「正準な」正規化でも発生しますが、これは文字の名前に字形をある程度意味する修飾語が付いていない(先ほどの例における U+FA19 は「CJK COMPATIBILITY IDEOGRAPH-FA19」、U+795E は「CJK UNIFIED IDEOGRAPH-795E」)ことから、Unicode の原則に従い、「互いに抽象的には同じ文字なのに、異なる文字符号が与えられている(文字のダブり)」という扱いになるためです[註 10]

もちろん、文字列同士を比較する場合には、互いに同じ形式(NFC、NFD、NFKC、NFKD)で正規化しなければなりません。例えば、一方の文字列を正規化形式 C(NFC、合成)で、他方の文字列を正規化形式 D(NFD、分解)で正規化して比較するのは妥当ではありません(バイト列として比較する場合には、文字符号化形式(例 UTF-8、UTF-16LE)も合わせる必要があります)。

Unicode 正規化の詳細については、The Unicode Standard – Core Specification(12.5 MB 大の Adobe PDF 文書ファイル)や Unicode Standard Annex #15 UNICODE NORMALIZATION FORMS をご覧ください。

ファイルシステムとファイル名の処理

2015 年 3 月中旬まで、教育用計算機システム(2012 年に導入されたものである ECCS2012)では、使用する Unicode 正規化の形式が統一されていませんでした。

ECCS2012 では、利用者領域(ホームディレクトリーが存在する場所。ここに、利用者が作成した文書ファイルなどが格納されます)を保存するためのファイルシステムが Network File System(NFS)になっています。NFS の現在の規格書である「Network File System (NFS) version 4 Protocol (RFC 3530)」によると、ファイル名(path name。データ型は utf8str_cs)は、Unicode 正規化を行った上で、文字符号化形式を UTF-8 として保存することになっています。Unicode 正規化の形式はこの規格書では指定されていません(つまり、NFC、NFD、NFKC、NFKD のいずれでもよいということです)。しかし、ファイルシステム NFS 自体が元々 UNIX で使用されていたこともあり、NFS は Linux サーバーで利用されることが多く、さらに、その Linux がファイル名の処理において Unicode 正規化の形式を正規化形式 C(NFC、合成)としていますので、NFS のファイル名に使用される Unicode 正規化の形式は正規化形式 C(NFC、合成)になっていることが多いのです。

ところが、その一方で、OS X(Mac OS X)のファイル名の処理においては、Unicode 正規化の形式がほぼ正規化形式 D(NFD、分解)(本来の正規化形式 D(NFD、分解)とは若干異なり、Unicode 文字符号が U+2000〜U+2FFF、U+F900〜U+FAFF、U+2F800〜U+2FAFF の文字は分解・統一の対象外です[註 10]。ちなみに、このようにして処理されたファイル名のデータ形式は「UTF-8-MAC」(文字符号化形式 UTF-8 の Macintosh 版、という意味でしょうか)と呼ばれることが多いのですが、これは、文字符号化形式の変換を行うシステム iconv の文字符号化形式で「UTF-8-MAC」と表示されるためですが、「UTF-8-MAC」自体は正式な文字符号化形式ではありません)です。そのため、多くの NFS の区画から取得したファイル名を OS X で利用するには、多くの NFS が利用しているであろう正規化形式 C(NFC、合成)を適用されたファイル名に対し、OS X が利用しているほぼ正規化形式 D(NFD、分解)な Unicode 正規化を行わなければなりません。しかし、OS X のアプリケーションソフトウェアでこの処理を行わないことがあるため、本来同一と見なされるべきファイル名が、NFS と OS X とでは異なる表現(バイト列)になることがあります。このような場合、ファイル名のバイト単位での比較では、本来同一と見なされるべきファイル名が同一と見なされず、結果としてファイルを探そうとしても見つけられなくなるのです。

経緯

  • 2004 年に導入された教育用計算機システム(ECCS 2004)から、基本システム(オペレーティングシステム)が Mac OS X(現: OS X)になりました。
  • OS X では通常、ファイル名の処理に「UTF-8-MAC」を使用しますが、教育用計算機システムのファイルサーバーではファイルシステムとして Network File System(NFS)を採用しており、このファイルシステムはファイル名に Unicode 正規化形式 C を適用しています。
  • 残念ながら、OS X が NFS(及び UFS)の領域を読み書きする際には、適切な Unicode 正規化を行ってくれません(samba ならやってくれるのですが)…。参考 1(Compatibility Notes)、参考 2 (リンク切れ)。この影響で、ファイルの移動ができませんでした。
  • それでも、OS X 10.6 Snow Leopard までは、ファイルの移動ができなくても、ファイルを開くことはできました。
  • OS X 10.7 Lion になり、Finder がファイルを読み書きする際には、必ず「UTF-8-MAC」で処理するようになりました。このため、ファイルを開く際にも、Unicode 正規化形式の不一致が発生し、ファイルを開く場合にも問題が発生するようになりました。
  • 2015 年 3 月 19 日、セキュリティーの都合による OS X の改訂(10.9)のついでに、ファイルサーバーにおいて適切な Unicode 正規化が適用されるようにシステムを変更しましたので、所謂「日本語ファイル名問題」は(教育用計算機システム(ECCS)の中での話ですが)解決に向かうことが期待されます。

備考

注意 この節は元々メモ書きのように作成されたもので、現在では積極的にメンテナンスしておりません。

  • 2013/4/30 Rubyで書き直したのでモジュール導入不要になりました
  • 業者の対応なので確定出来ませんが、mount_nfsに-o nfcをつければ解決されるかもしれません。続報をお待ちください。
  • http://d.hatena.ne.jp/neil_sk/20121201/1354390462
  • 2013/5/29: 業者が検証中との情報が入りました
  • 公式FAQにも該当情報はある:https://www.ecc.u-tokyo.ac.jp/announcement/2013/04/24_1724.html
  • 2015/3/19 OSXバージョンアップに伴い、NFSがNFCでマウントされることとなりました

後註

  1. ここでは、Unicode 文字列において、分解済み文字を合成したり、合成済み文字を分解したりするために、Ruby 1.9 以降で使用できるメソッド String#encode を使用しています。このメソッドの引数に「’UTF-8’」を指定することで合成済み文字が、「’UTF-8-MAC’」を指定することで分解済み文字が、それぞれ生成されることが期待されています。しかし、これらの処理は、Unicode 正規化の正規化形式 C(NFC、合成)や正規化形式 D(NFD、分解)とは次の点で異なります。一つは、Unicode の CJK 互換漢字のように、置換の対象外の文字がある(例 CJK 互換漢字 U+FA19 は CJK 統合漢字 U+795E(「神」)に置換されません)という点です。もう一つは、親文字の直後に並べられている結合文字(親文字の後ろに付き、文字の形状を変化させます。ダイアクリティカルマークなどがこれに該当します)の順序を、Canonical_Combining_Class(正準結合文字クラス、ccc)が小さい方から順に並べ替える(親文字(ccc が 0 の文字)を越えては移動しないものとします)ことをしないという点です。この影響で、Ruby のメソッド String#encode で引数に「’UTF-8’」を指定しても、結合文字が親文字から離れていると、本来は合成可能にも関わらず、合成されません。つまり、この方法では、Unicode 正規化を行うことはできません。しかし、この方法でも「日本語ファイル名問題」は多くの場合には解決されるはずです。解説も併せてご覧ください。参考までに、この方法では Unicode 正規化が実現できないことを示すサンプルプログラムを掲載いたします(UTF-8 テキストファイルとして保存してください。Ruby 1.9.3 や Ruby 2.1.3 で動作確認を行いました)。

    # coding: utf-8
    
    str1 = "\u{004F}\u{0300}\u{0339}\u{0338}"  # 入力: U+004F U+0300 U+0339 U+0338
    puts str1.codepoints.map { |c| "U+%04X" % c }.join(" ")
    # Unicode 正規化すると、U+004F U+0300 U+0339 U+0338 は
    #  正規化形式 C(NFC、合成)で U+00D2 U+0338 U+0339 に、
    #  正規化形式 D(NFD、分解)で U+004F U+0338 U+0339 U+0300 に、
    # それぞれなるはずですが…。
    str2 = str1.encode("UTF-8-MAC")  # 出力: U+004F U+0300 U+0339 U+0338
    puts str2.codepoints.map { |c| "U+%04X" % c }.join(" ")
    str3 = str2.encode("UTF-8")  # 出力: U+00D2 U+0339 U+0338
    puts str3.codepoints.map { |c| "U+%04X" % c }.join(" ")
    # …ならないことが分かります。
    
    puts ""
    
    str4 = "\u{004F}\u{0339}\u{0338}\u{0300}"  # 入力: U+004F U+0339 U+0338 U+0300
    puts str4.codepoints.map { |c| "U+%04X" % c }.join(" ")
    # Unicode 正規化すると、U+004F U+0339 U+0338 U+0300 は
    #  正規化形式 C(NFC、合成)で U+00D2 U+0338 U+0339 に、
    #  正規化形式 D(NFD、分解)で U+004F U+0338 U+0339 U+0300 に、
    # それぞれなるはずですが…。
    str5 = str4.encode("UTF-8-MAC")  # 出力: U+004F U+0339 U+0338 U+0300
    puts str5.codepoints.map { |c| "U+%04X" % c }.join(" ")
    str6 = str5.encode("UTF-8")  # 出力: U+004F U+0339 U+0338 U+0300
    puts str6.codepoints.map { |c| "U+%04X" % c }.join(" ")
    # 結合文字 U+0300 が親文字 U+004F から離れていると、これらが合成されない(U+00D2 にならない)ことが分かります。

    なお、Ruby については、バージョン 2.2.0 で Unicode 正規化に対応したようです(ここでも動作確認を行う予定)。

  2. これを実現するために、例えば、OpenType フォントの高度な組版機能に対応したアプリケーションソフトウェアは、OpenType フォントに記述されている文字位置合わせ表(GPOS)および文字置換表(GSUB)を参照し、その情報に従って、印字位置(pen points)を移動させたり、文字に対応するグリフ番号(glyph indices、GID)を別のものに置換したりします。
  3. 1978 年版の JIS C 6226「情報交換用漢字符号系」(後の JIS X 0208「7 ビット及び 8 ビットの 2 バイト情報交換用符号化漢字集合」)である JIS C 6226-1978 では、分解済み文字として「合成用丸」(2 区 94 点。Unicode で言うところの[U+20DD COMBINING ENCLOSING CIRCLE])が用意されていました。この「合成用丸」の用途は、数字と組み合わせて丸数字として合成することにありました。しかし、これは合成用途としては普及されないまま、その後の改訂(JIS X 0208:1997)で単なる「大きな丸」に変更され、合成の対象から外されました。
  4. Unicode における半角文字・全角文字の指定の参考情報については、Unicode Standard Annex #11 EAST ASIAN WIDTH をご覧ください。 ちなみに、半角英数字と全角英数字の具体的な使い分けですが、横組(横書き)では、基本的に専ら半角英数字を使用します(特に、欧文組版では、欧文用の英数字(半角英数字と文字符号が同じ)を使用するものとし、全角英数字を使用してはなりません)。但し、和文組版における 1 桁の英数字については、日本語の文字が基本的に全角を基準としており、字間を空けずに行端が揃って見えるのが美しいとされていますので、1 桁の英数字を全角英数字で使用するという流儀があります。この場合、全角英数字が半角英数字とデザインが大きく異なることのないように注意するのがよいでしょう。一方、縦組(縦書き)では、縦中横で 2 桁の数字を書く場合などを除いては、全角英数字がよく使用されます。縦組では、縦中横を指定せずに半角英数字を入力すると、文字が時計回りに 90 度回転してしまいます。
  5. Shift_JIS や Unicode などにおける所謂半角文字は JIS X 0201「7 ビット及び 8 ビットの情報交換用符号化文字集合」(旧: JIS C 6220)、所謂全角文字は JIS X 0208「7 ビット及び 8 ビットの 2 バイト情報交換用符号化漢字集合」に由来します。しかし、JIS X 0201 や JIS X 0208 は基本的に字形や文字幅を規定するものではありません(例外は、印字可能な文字(「図形文字」)は前進しなければならない(字送り幅が正)ということだけです)ので、JIS X 0201 や JIS X 0208 に半角文字や全角文字の概念はありません。また、JIS X 0208 は文字集合としては JIS X 0201 の上位集合(superset)ですから、JIS X 0208 があれば JIS X 0201 は不要になるはずでした。しかし、書籍「文字符号の歴史 欧米と日本編」(著: 安岡孝一、安岡素子 出版: 共立出版)によると、元々 JIS X 0208 は JIS X 0201 で規定されている文字を順番も維持してそのまま取り入れることが予定されていましたが、手軽にデータの並べ替えを行いたいなどに要求により、文字の順番が入れ替えられてしまいました。既に JIS X 0201 を基にデータ処理を行うシステムを開発していた企業は、JIS X 0208 を採用する上で、(JIS X 0201 における文字の順番が入れ替えられてしまった以上)過去のシステムで作成されたデータを新システムに移行するのが面倒になる(単純な計算式で新しい文字符号を求めることができず、比較的大きな文字符号対応表を用意しなければなりません)ことを理由に、JIS X 0208 とは別に JIS X 0201 をそっくりそのまま取り入れた文字集合を独自に定義しました(Shift_JIS はそのようにして作成された文字集合・文字符号化形式の一つです)。そして、システムの慣用あるいは都合により、JIS X 0201 由来の文字は半角文字、JIS X 0208 由来の文字は全角文字として使用されるようになりました。
  6. 「葛」の正式な字体を使用する方法はいくつかあります。一つの方法としては、Unicode では、異体字選択子を使用することで表現でき、「東京都葛飾区」の「葛」(通称: 人かつ)の文字符号は「U+845B U+E0101」および「U+845B U+E0103」、「奈良県葛城市」の「葛」(通称: ヒかつ)の文字符号は「U+845B U+E0100」および「U+845B U+E0102」です。別の方法としては、フォントを切り替えるというもので、「東京都葛飾区」の「葛」は JIS C 6226:1978 や JIS X 0213:2004 の例示字形に従って制作されたフォント、「奈良県葛城市」の「葛」は JIS X 0208:1983(旧: JIS C 6226:1983)や JIS X 0208:1990 の例示字形に従って制作されたフォントを指定すると、デフォルトの状態で表示・印刷されます。フォントの字体切替え(例 OpenType フォントの文字置換表(GSUB))が有効であれば、それを利用することもできます(例 OpenType フォントの文字置換表でタグ「jp78」を指定すると、JIS C 6226:1978 の例示字形で表示・印刷されます)。さらに別の方法としては、Adobe-Japan1 のように、字形を明確に規定した文字集合で文字番号(CID)を指定するというもので、Adobe-Japan1 における文字番号は、「東京都葛飾区」の「葛」では「CID+7652」、「奈良県葛城市」の「葛」では「CID+1481」になります。フォント固有のグリフ番号(GID)を指定するという方法もありますが、フォント間で互換性がない(ので、後で簡単にフォントを変更するわけにはいかない)という難点があります。
  7. 詳細については、「The Unicode Consortium – Frequently Asked Questions – Characters and Combining Marks」をご覧ください。ここでは、その中で最も直接的な説明になっている部分を引用します(2014 年 12 月現在)。
    • Q: Why are new combinations of Latin letters with diacritical marks not suitable for addition to Unicode? (日本語訳 ラテン文字にダイアクリティカルマークを合成した文字(合成済み文字)が Unicode への追加に適していないのはどうしてですか?)
    • A: There are several reasons. First, Unicode encodes many diacritical marks, and the combinations can already be produced, as noted in the answers to some questions above. If precomposed equivalents were added, the number of multiple spellings would be increased, and decompositions would need to be defined and maintained for them, adding to the complexity of existing decomposition tables in implementations. Finally, normalization form NFC (the composed form favored for use on the Web) is frozen – no new letter combinations can be added to it. Therefore, the normalized NFC representation of any new precomposed letters would still use decomposed sequences, which can already be expressed by combining character sequences in Unicode. Nothing would be gained by adding the letter with diacritical mark as a precomposed character; on the contrary, adding such a letter would add one or more multiple spellings to be reckoned with, incrementally complicating all Unicode implementations for no net gain. (日本語訳 それにはいくつかの理由があります。一つには、(原文の)先述のいくつかの質問に対する回答で述べたように、Unicode は多数のダイアクリティカルマークを符号化しており、(それを利用して、表示・印刷時に合成の対象になるような基底文字と結合文字の)組合せを生成することが既に可能になっているからです。もし、(基底文字と結合文字の組合せに対する)合成済みの同等物(文字)が追加されたら、(一つの文字に対する、意味は同じだが表現が異なるという意味での)重複した綴り方の数が増えてしまい、(それらに対する)分解の仕方を定義し、メンテナンスしなければならず、そのことは既存の文字分解表を実装の点でさらに複雑にしてしまいます。もう一つには、正規化形式 C(NFC)(Web での利用に適した合成済み形式)は凍結されており、それに新しい合成済み文字を追加することは不可能です。それゆえに、いかなる新しい合成済み文字に対して正規化形式 C で正規化された表現は分解済み文字列を使用し続け(文字符号上は合成の対象にならない)、その分解済み文字列は Unicode では既に結合文字列(文字符号上は合成されないが、表示・印刷時に初めて合成の対象になる文字列)を使用して表現できるようになっているからです。ダイアクリティカルマーク付きの文字を合成済み文字として追加することで得られるものは何もなく、反対に、そのような文字は、一つ以上の重複した綴り方を追加で考慮しなければならず、正味の意義なく全ての Unicode の実装物(ソフトウェアなど)をより一層複雑なものにしてしまいます。)
  8. 例えば、OpenType フォントの文字置換表(GSUB)には、全角字形を表示するためのタグ「fwid」と、半角字形を表示するためのタグ「hwid」が用意されています。但し、これらを実装するかどうかは各フォントに依存します。
  9. 書籍「文字符号の歴史 欧米と日本編」(著: 安岡孝一、安岡素子 出版: 共立出版)によると、次のような話があります。元々、ISO/IEC 10646(UCS; Universal Coded Character Set)は、中国語、日本語、韓国語の文字を定義するにあたり、当時各国で現行規格とされていた規格で定義されている文字を、異なる言語間で文字を包摂(統合)することなく、そのまま 16 ビット空間に収録しようとしていました。実際、漢字は 16 ビット空間(最大で約 65,000 字)に収まると信じられていました。確かに、当時の規格で定義されている漢字は各国でせいぜい数千字ですから、16 ビット空間に収めるのは十分に可能だったことでしょう。しかし、中国が当時策定中で、収録字数を大幅に増加させた規格の文字を盛り込もうとしたために、このままでは 16 ビット空間では間に合わないことが判明しました。そこで 32 ビット空間に拡張することが提案されましたが、中国はこれに反対し、中国語、日本語、韓国語の漢字を別々に収録せず、統合することを提案しました(日本はこれに反対する立場を取っていました)。結局、これらの言語間の漢字を包摂のもとで上手に統合するというテクニックにより、膨大な数の漢字を 16 ビット空間に格納することに成功しました。 ちなみに、他の文字符号化形式に言及すると、ISO/IEC 2022 では、エスケープシーケンスを利用して文字集合を選択する機能があります(逆に、これにより文字列処理が面倒になるという欠点があります)。プログラミング言語 Ruby はバージョン 1.9.0 以降で、文字列オブジェクトに文字符号化形式の情報を含める「code set independent」(CSI)方式を採用しています。Unicode については、当初は書体・フォントの指定で異なる字形に対処することが想定されていましたが、実際には書体・フォント情報を含まないプレーンテキスト形式での運用が多くありましたので、根本的な解決策にはなりませんでした。今後は、異体字選択子(Variation Selectors)が解決してくれるかもしれません(2015 年 3 月現在では、日本語の文字に適用できるものしか用意されていません)。
  10. Apple は Unicode Technical Committee(UTC)に対して、例えば、CJK 互換漢字は正規化による置換先になる CJK 統合漢字と字形が異なる(例 U+FA19 から U+795E(「神」)への置換)のだから、正準分解(Canonical Decomposition)の対象外とするような正規化を新設することを提案しましたが、却下されました。結局、字形が異なるもの同士の正式な区別は、元々 Adobe Systems によって提案された、異体字選択子(Variation Selectors)の適用(例 U+FA19 に相当する字形を得たい場合には、「U+795E U+FE00」(Standardized Variant)、「U+795E U+E0100」(Ideographic Variation Adobe-Japan1)、または「U+795E U+E0103」(Ideographic Variation Hanyo-Denshi)とします(参考))によって実現されました(参考)(参考)。しかし、この部分のフォントにおける対応は、フォントによって様々なので、まだ安心できないところではあります(もちろん、基本システム(オペレーティングシステム)やワードプロセッサーなどがそのような機能に対応していることが大前提です)。
    • 例 IPAmj 明朝は「Hanyo-Denshi コレクションに準拠して IVS を実装してあります」と主張されています。IPAmj 明朝 Ver. 003.01 では、Standardized Variant と、Hanyo-Denshi および Moji_Joho に由来する Ideographic Variation が収録されていることになっています(Adobe-Japan1 に由来する Ideographic Variation は収録されていません)が、CJK 互換漢字として収録されている異体字(CJK 互換漢字の例示字形として表示されている異体字)、および、Standardized Variant として収録されている異体字は、Hanyo-Denshi に由来する Ideographic Variation としては収録されていません。例えば、「U+FA19」(CJK 互換漢字)と「U+795E U+FE00」(Standardized Variant)では正しく、「神」の異体字で左半分が「示」になっているものが表示されますが、それと同じ字形で表示されることが期待される「U+795E U+E0103」(Ideographic Variation Hanyo-Denshi)では「神」(左半分が「ネ」)のまま(つまり、単に「U+795E」(CJK 統合漢字)を指定した場合と同じ)になってしまいます(同様のことが書かれている記事はこちら)。
公開日
更新日
編集者
Akira Takeuchi
タグ