名城大学理工学部 応用化学科 永田研究室
トップ 教育 研究 プロフィール アクセス リンク キャラクター ブログ
トップ  >  ブログ  >  MacOS の UTF に振り回された話

 

ブログ「天白で有機化学やってます。」 ブログ「天白で有機化学やってます。」
< 理系大学の「女子枠」 | ブログトップ | 「学校がウソくさい」(藤原和博著、朝日新書) >

MacOS の UTF に振り回された話2024/01/15(月)

仕事柄、大量のファイルを扱うことが多いので、Python やら Ruby やらでスクリプトを書いて、少しでも負荷を軽減する工夫をしています。日本語文字の扱いがなかなか厄介ですが、UTFへの統一がだいぶ進んできたので、かなり楽になりました。

ところが、まだトラップが残っていたのです。しかも、めっちゃわかりにくいトラップです。

発端はこういうことです。学生から受け取ったレポートのファイル名が重複していて、ダウンロードのときに "-1" というのがついていました。

answers.csv の中にファイル名が入っているのですが、それはオリジナルの「高分子 レポート.pdf」のままになっています。いつもは、素直にそこに "-1" を書き足して処理しているのですが、今回はなぜか出来心で、ファイル名の方も Finder 上で変更してしまったのです。

いつものようにスクリプトで処理しようとすると、つれなくエラーを返されました。

高分子 レポート-XXX.pdf is not found

え、なんでよ? そこにあるやん。スクリプトのあちこちに print 文を入れて処理の流れを確かめてみましたが、どこにも間違いはありません。ファイル名の「高分子 レポート-XXX.pdf」と csv から読み込んだ「高分子 レポート-XXX.pdf」がそれぞれ変数に入っているのですが、それらが等しくならないのです。

bname is "高分子 レポート-XXX"
a[22][6] is "高分子 レポート-XXX"
bname == a[22][6] is false

もうおわかりの方もいらっしゃるでしょう。1行目の「ポ」は "U+30DB,U+309A (Canonical Decomposition)" で、2行目の「ポ」は "U+30DD (Canonical Composition)" なのです。このスクリプトは Ruby で書いてあって、bname は Dir.glob("*.pdf") で取得、つまりファイルシステムの文字コードです。配列 a は csv ファイルから読み込んでいて、シフトJISコードを UTF に内部変換しています。同じ文字が、UTF で2通りの違った表現になってしまっているわけです。

UTF には、濁点・半濁点やアクセント文字の取り扱いで2つの異なる方式があります。MacOS のファイルシステムでは、濁点・半濁点・アクセント記号を「元の文字と記号文字」の2つに分割して扱います(NFD = Normalization Form Canonical Decomposition)。一方、Windows や Linux のファイルシステムでは濁点・半濁点・アクセント記号がついた文字を「元の文字とは別の1文字」として扱う(NFC = Normalization Form Canonical Composition)方式が採用されています。UTF以前の文字コードでは、濁点・半濁点つきの文字は元の文字とは別のコードを割り当てるのが一般的でした。「昔のコードとの互換性」を重視すれば NFC を採用するのが自然でしょう。(参考:「ファイルアップロードではNFC/NFD問題に気をつけろ!~MacファイルシステムにおけるUnicode正規化の闇~」Hacobell Developers Blog 2023/08/29)

Apple は「とにかくレガシーは滅ぼす」という鋼の意志を持つ会社です。この文化に沿って、ファイルシステムの文字コードとして NFD を採用したと思われます。その結果、ある OS のバージョンでは、「Finder でファイルをダブルクリックしても開かない」という大変みっともないバグを生産してしまったそうです。自分で自分の首しめとるやん。(参考:「macOS 13.3 VenturaではNFC/NFD問題が再発し、濁音やアクセント記号が付いたファイルをダブルクリックしてもアプリで開けない不具合があるので注意を。」AAPL Ch. 2023/04/02)

最初の話に戻ります。「レポート.pdf」という名前のファイルはこれまでも何度も受け取っていて、Ruby スクリプトで処理しても何の問題も起こしていませんでした。これは、ダウンロードしたときには NFC 形式のファイル名で保存されていたからです。今回問題を起こしたのは、Finder 上でファイル名を変更したためでした。この操作のときに、「ポ」が NFD 形式の「ポ」に変わってしまい、スクリプトから見れば「別の文字」に見えるようになってしまったわけです。めんどくせー!

Rubyでの解決策は下の通りです。Dir.glob() でファイル名を読み込んだ後、NFC形式に強制変換します。

pdfs = Dir.glob("*.pdf")
pdfs.each { |pname|
  pname.encode!('UTF-8', 'UTF-8-MAC')
  pname.force_encoding('UTF-8')
}

なお、Macファイルシステムのエンコーディングは、完全な NFD ではなくて、一部の文字は分解しない、という方針のようです。(参考:「Ruby: UTF-8-MAC(UTF-8-HFS) を UTF-8 (NFC) に変換する」 Sarabande.jp 2014/03/02)文字コードの取り扱いは、それぞれの言語文化と密接に関わっていますから、なかなかデリケートな問題です。ということは、こういう面倒くささは、いつまでたっても何らかの形で残るんでしょうね……。

(2024.6.10.追記) NFC形式への強制変換は、もっと簡単な方法がありました。Ruby 2.2 以降で実装されている String#unicode_normalize! を使います。

pdfs.each { |pname|
  pname.unicode_normalize!
}
< 理系大学の「女子枠」 | ブログトップ | 「学校がウソくさい」(藤原和博著、朝日新書) >