物理の駅 by onsanai

Physics Station → PhSt 質問・疑問・間違いの指摘は、コメントに書くか、直接伝えるときっと良いことがあります。主にWindows or Ubuntu用の記事です

pythonでShift-JISやasciiのファイルを一括でUTF-8 with BOMへ変換する

Visual Studio用。 UTF-8 BOMなしの場合は、/source-charset:utf-8 をつけておかないと、エライことになると同僚から報告を受けた。この話は後述する。

なので、可能な限りUTF-8 with BOMに自動で変換してみる。以下のコードを実行する前に必ずバックアップを取っておくか、変換するコードはコメントアウトして実行すること(重要)。

import chardet
import glob

# 探索する拡張子リスト
extensions = ["cpp", "hpp", "c", "h", "cxx", "hxx"]


def check(filename):
    # バイナリで開く
    with open(filename, 'rb') as f:
        b = f.read()
    # 文字コードを検出(Shift-JISへの精度は高くない)
    det_chardet = chardet.detect(b)

    # UTF-8 with BOM以外の場合はprint
    if det_chardet["encoding"] != "UTF-8-SIG":
        print(det_chardet, filename)

    # ascii100%のコードをUTF-8 with BOMへ変換
    if det_chardet["encoding"] == "ascii" and det_chardet["confidence"] == 1.0:
        with open(filename) as f:
            contents = f.read()
        with open(filename, "w", encoding="utf_8_sig") as f:
            f.write(contents)
        print("ascii -> UTF-8-SIG")

    # SHIFT_JIS(と何故か間違えられやすいWindows-1252)のコードをUTF-8 with BOMへ変換
    if det_chardet["encoding"] == "SHIFT_JIS" or det_chardet["encoding"] == "Windows-1252":
        with open(filename, "r", encoding="shift-jis") as f:
            contents = f.read()
        with open(filename, "w", encoding="utf_8_sig") as f:
            f.write(contents)
        print("SHIFT_JIS -> UTF-8-SIG")

    return det_chardet["encoding"]


# エンコードリスト
encodings = {}

for extension in extensions:
    # cd以下全部を再帰的に探索
    files = glob.glob(f"**/*.{extension}", recursive=True)
    for filename in files:
        # パッケージフォルダ内は無視する
        if "\\packages\\" in filename:
            continue
        det_encoding = check(filename)
        # 検出したエンコードリストを辞書型で集計する
        if det_encoding not in encodings:
            encodings[det_encoding] = 1
        else:
            encodings[det_encoding] += 1
    print()

# 集計結果
print(encodings)

そもそもの発端である同僚からの報告を簡単に書いておく。

UTF-8 BOMなしのコードは、VCのコンパイラからはSHIFT-JISと推定される。UTF-8で書いていたコードのコメント行の末尾が文字化けし、以下のような例で

//コメント
if()
{
}

が、

//コメントif()
{
}

と認識されてしまい{}内が常に実行されるという問題が発生したとのこと。仕様上仕方がない(バグではない)ので、気をつけるしかない。