MNTSQ Techブログ

リーガルテック・カンパニー「MNTSQ(モンテスキュー)」のTechブログです。

ファイルシステムとS3でのユニコード正規化の関係を調べてみた2021

MNTSQ Tech Blog TOP > 記事一覧 > ファイルシステムとS3でのユニコード正規化の関係を調べてみた2021

f:id:NPoi:20210316185237j:plain

こんにちは、MNTSQでSREとして勤務している中原といいます。 プライベートも含めて、技術記事は久しぶりな気がします。がんばります。

さて、さっそくですが、日本人にとって、あるいは、韓国の方や中国の方も含めて、コンピュータ上でそれぞれの国の言葉を扱おうとしたときに苦労するのが文字コードです。

かつては(あるいは今も)、Shift JIS、EUC-JPなど、OSや環境などによって使われる文字コードが異なり、相互の連携や、同じOSでも設定次第で大いに苦労したものでした(と聞いておりますし、個人でPCを楽しんでいたときには苦しんだりした記憶があります)。

そうこうしているうち、多くのOSで標準的な文字コードとしてUnicodeが採用されるようになりました。Windowsでは内部でUTF-16LEを採用しています。Linuxでは、UTF-8を標準とすることが多くなりました。

Unicodeに統一がはかられるにつれ、かつてと比べ、環境差異の埋めやすさも、使える文字の種類についても増え、大きく利便性があがりました。「とりあえずUTF-8」などと、乱暴でも一杯目のビールよろしく指定しておけば、多くの場合困ることはなくなりました。

一方で、一言でUnicodeを使うという文脈の中にも様々な切り口から見た方式の種類があり、それぞれの違いを意識しなければいけない場面も出てきました。たとえば、UTF-8やUTF-16LEなどといった符号化スキーマの違いなどが有名ですが、この記事では”正規化 (Form Normalization)という側面について記述していきたいと思っています。

思っていますといっても、内容としては実は4年ほど前に個人のQiitaに書いた記事の改訂版になります。

かいつまんでまとめると、

  • 当時、私はS3に日本語ファイル名のものをアップロードした
  • awscliからアップロードしたファイルを検索しようとしてみたがマッチしなかった
  • 原因はUnicode正規化の方式が、ファイル名と検索クエリの間で異なるせいで、人間には同じ見た目でも、内部的にコードが異なっておりマッチしなかった
  • なので、どういうアップロードのしかたをしたときにS3でのファイル名がどういったフォーマットになるのかというのをまとめてみた

というものでした。

正規化の種類

ユニコードの正規化とはなんでしょうか。

他のサイトにわかりやすいサイトも多くありますのでここでは割愛しますが、簡単に言えば、「が」という文字を見たときに、これを「が」という一つの文字として扱うか、「か」+「゛(濁点)」として扱うか、という点の取り決めのことです。濁点、半濁点の他、異字体(「神」と「神」など)、半角全角まで含めるか(「カ」と「カ」を同等に扱うか)などの観点でも扱いが変わります。

これらの扱いの違いによってNFC、NFD、NFKC、NFKDという4つの正規化方式があります。

Unicodeの規定では、この正規化の方式が異なっていても、それぞれ同様に扱うように決められていますが、必ずしもすべてのアプリケーションの実装がそうなっているわけではなく、また、ユースケースによっては厳密にわけなければいけない場合もあるわけで、上記のように検索に引っかからないこともあるわけです。

この正規化形式は、ソフトウェアやOSの処理系、ファイルシステムを経ることにより、暗黙的に変化する場合があります。それによって、見た目上は一致している文字列が内部では異なる文字列として扱われると言った不具合が出てくることがあるわけです。

本記事で試すこと

二つの内容を実施しました。

まず、Pythonのプログラムを使ってMacWindowsそれぞれの環境で、NFC、NFD、NFKC、NFKDそれぞれの正規化方式と、なにも指定しない場合でファイルを作ってみて、ファイル名のバイト列がどのように生成されるかを確認してみます。

作るだけの簡単なプログラムなので、以下のようなものになります。文字列は、それぞれ濁点の含まれる文字ですね。なお、chr(0xfa19)は"神"です。

import os
import unicodedata


FORMS = ['default', 'NFC', 'NFKC', 'NFD', 'NFKD']
STRINGS = ["1_が", "2_ガ", "3_ガ", "4_㍊", "5_神", "6_{0}".format(chr(0xfa19))]

def make_files():
    for form in FORMS:
        os.mkdir(form)
        for string in STRINGS:
            if form == "default":
                with open(form + "/" + string, "w") as f:
                    f.write("")
            else:
                with open(form + "/" + unicodedata.normalize(form, string), "w") as f:
                    f.write("")

次に、上記の方法で作ったファイルをいくつかの方法でAmazon S3にアップロードし、S3でどういった扱いになるのかを再びPythonのプログラムで確認していきます。

def list_files():
    session = boto3.session.Session()
    s3 = session.resource("s3")

    bucket = s3.Bucket(BUCKET_NAME)
    for i in bucket.objects.all():
        print("{0}: {1}".format(i.key, i.key.encode("utf-8")))

今回はMacで4つのアップロード方法、Windowsで5つのアップロード方法を試します。Macでは、awscliでのs3 cpとs3 sync、boto3によるPythonプログラムによるアップロード、ChromeでのAWSコンソールから。Windowsではaws cliでのs3 cpとs3 sync、boto3によるPythonプログラムによるアップロード、ChromeでのAWSコンソールから、そしてIE11でのAWSコンソールからのアップロードも追加しています。

正規化方式が5種類(なにもしないを含む)、文字が6種類、アップロードの方法が9種類とファイルシステムへの書き込み結果ということで、かけ算すると270種類の結果ができあがります。

270種すべてをここで掲示するのは難しい分量的にも難しいですし、そもそもすべてを貼る理由もないので、結果から言えることをまとめていきたいと思います。

なお、結果のCSVこちらにアップロードしています。

実施してみてわかったこと

  1. S3はファイル名に対してなにもしていない

    ファイル名の正規化方式について、S3はなにか手を加えているかというと、なんも手を加えないですし、意思を持たないようです。原則、ローカルで作られたファイルは、ローカルで手を加えられない限りはそのまま上がるようです。

    ただし、アップロードの過程で、一部の方法では透過的に正規化がかかる場合はあるようです(4.を参照のこと)。

  2. aws s3の中で方法によって異なっていた結果は是正された模様

    5年前試したときには、aws s3 cp と aws s3 sync でアップロードした場合に結果が異なるということがありました。今回試したなかで確認した限りでは、この現象は是正されていたようです。これはよいことだと思います。

  3. 神は正規化されるといなくなる

    これは知っている方には当たり前な話かもしれませんし、S3にも直接関係しませんが、ユニコード正規化をかけてしまうと「神」は「神」になってしまいます。これはPythonのunicodedataモジュールに限らないものです。

    正規化しなければいけない場面や、正規化方式を統一するとデータの扱いが楽になる場面は多いのですが、データソースが変化してしまう情報も出てくる点については考慮が必要です。

  4. Macはひどい

    1.のなかで「基本的にはそのままのことが多いようです」という曖昧な書き方をしたのはこの処理のせいです。Macの処理系(おそらくFoundation APIのせい)を通すと問答無用に正規化方式がNFD/NFKDになるようです(参照)。これは他の方の記事でも指摘されていることで、Macの上で無理矢理にでもNFC/NFKCで処理したい場合にはPythonなどの処理系の中で明示的に正規化方式を指定して扱う必要がありそうです。

    この点については、MacよりもWindowsの方が筋がいい挙動だなと思ったのは正直なところです。

  5. (番外)IE11はひどい

    これは、正規化とは直接関わらない内容なので、ちょっとずれる話なのですが、IE11はアップロードの際にフォルダでのアップロードに対応していません。S3にファイルをアップロードする際には、ウェブインターフェースでそれぞれのディレクトリを作ってから、一つ一つファイルをアップロードする必要があります。ただ、単純に面倒くさかったです。

    なお、ファイル名についての挙動は自然なものでした。

まとめ

結論としては、以前書いたものと大きくは変わりません。基本的にシステムで扱うファイル名などには、極力マルチバイト文字は使わない方がいいという簡単なものです。

とはいえ、ファイル名の情報を保持しなければいけない場面も多々発生しますし、処理の順番などにも気を遣わなければなりません。

また、今回Macでの扱いには注意が必要ということ痛感しました。今はコンテナを用いた開発が盛んですので、直接Macの上で開発する、ということを避けられる状況は整っていますが、開発はMac、本番環境はLinuxなど、環境が変わる場合には挙動も変わり得ますので、そういった考慮をした上で開発・運用した方が良さそうです。

冒頭にも書きましたが、コンピュータの上で日本語を使う際には否応なく文字コードを意識しなければなりません。今の苦しみ方は過去の苦しみ方とはまた異なりますが、今なお苦しみ続けなければいけないことには変わりがありません。

将来に渡って、なんらか根本的な解決策が発明されることを望みながら締めたいと思います。

この記事を書いた人

f:id:NPoi:20210316190125j:plain

中原大介

MNTSQ社でSREをやってます。最近は乗り鉄してることが多いです。