頑張らないために頑張る

ゆるく頑張ります

Windowsのエンコードで悩みたくないからPythonスクリプトを作った

Posted at — Aug 27, 2025

はじめに

WindowsとMac、あるいはLinuxとの間でテキストファイルをやり取りしたとき、文字化けに遭遇して「うわっ・・・」ってなった経験ありますか?自分はあります、しこたまあります

原因の多くは、Windowsが標準で使うShift_JIS (SJIS)と、MacやLinuxで標準のUTF-8という文字コードの違いです。メモ帳で書いた文章が、別の環境だと謎の記号の羅列になってしまう、あの現象です。

毎回テキストエディタで開き直して、エンコーディングを指定して保存し直すのも面倒くさい・・・。特にそういうファイルが大量にあるときは、もう考えただけでうっとりうんざりします。

そこで、この面倒な作業をコマンド一発で解決するために、シンプルなPythonスクリプトをざっと作ってみました。今日はその紹介です。

作成したスクリプトの概要

今回作ったのは、テキストファイルのエンコーディングをSJISとUTF-8で相互変換できるCUIツールです。

主な機能はこんな感じです。

「ちょっとこのフォルダのテキスト、全部UTF-8にしておきたいな」なんて時に、サッと使えるツールを目指しました。

Pythonスクリプトのコード

作成したスクリプトのコードは以下の通りです。

import os
import sys
import glob
import argparse
from pathlib import Path


def detect_encoding(file_path):
    """ファイルのエンコーディングを検出する"""
    encodings = ['utf-8', 'shift_jis', 'cp932', 'euc-jp', 'iso-2022-jp']
    
    for encoding in encodings:
        try:
            with open(file_path, 'r', encoding=encoding) as f:
                f.read()
            return encoding
        except UnicodeDecodeError:
            continue
        except Exception:
            continue
    
    return None


def convert_file_encoding(input_file, from_encoding, to_encoding, overwrite=False, quiet=False):
    """ファイルのエンコーディングを変換する"""
    try:
        # ファイルを読み込み
        with open(input_file, 'r', encoding=from_encoding) as f:
            content = f.read()
        
        if overwrite:
            # 元ファイルを上書き
            output_file = input_file
        else:
            # 新規ファイルを作成
            input_path = Path(input_file)
            if to_encoding.lower() == 'utf-8':
                suffix = '_utf-8'
            elif to_encoding.lower() in ['shift_jis', 'sjis', 'cp932']:
                suffix = '_sjis'
            else:
                suffix = f'_{to_encoding.lower()}'
            
            output_file = input_path.parent / f"{input_path.stem}{suffix}{input_path.suffix}"
        
        # ファイルを書き込み
        with open(output_file, 'w', encoding=to_encoding) as f:
            f.write(content)
        
        if not quiet:
            print(f"変換完了: {input_file} -> {output_file}")
            print(f"  {from_encoding} -> {to_encoding}")
        return True
        
    except Exception as e:
        if not quiet:
            print(f"エラー: {input_file} の変換に失敗しました - {str(e)}")
        return False


def get_target_files(path, quiet=False):
    """指定されたパスからtxtファイルとmdファイルのリストを取得する"""
    target_files = []
    non_target_files = []
    valid_exts = ['.txt', '.md']
    
    if os.path.isfile(path):
        # ファイルが指定された場合
        if Path(path).suffix.lower() in valid_exts:
            target_files.append(path)
        else:
            non_target_files.append(path)
            if not quiet:
                print(f"警告: {path} は.txtまたは.mdファイルではありません。スキップします。")
    elif os.path.isdir(path):
        # フォルダが指定された場合
        all_files = glob.glob(os.path.join(path, '*'))
        for file in all_files:
            if os.path.isfile(file):
                if Path(file).suffix.lower() in valid_exts:
                    target_files.append(file)
                else:
                    non_target_files.append(file)
        
        # 非対象ファイルの警告(quietモードでない場合のみ)
        if not quiet and non_target_files:
            print(f"警告: {len(non_target_files)}個の非.txt/.mdファイルをスキップしました。")
            # ファイル数が多い場合は個別表示を制限
            if len(non_target_files) <= 5:
                for file in non_target_files:
                    print(f"  スキップ: {file}")
            else:
                for file in non_target_files[:3]:
                    print(f"  スキップ: {file}")
                print(f"  ... その他 {len(non_target_files) - 3} ファイル")
        
        if not target_files:
            if not quiet:
                print(f"警告: {path} に.txtまたは.mdファイルが見つかりませんでした。")
    else:
        if not quiet:
            print(f"エラー: {path} は存在しないパスです。")
    
    return target_files


def main():
    # コマンドライン引数の設定
    parser = argparse.ArgumentParser(
        description='テキストファイルのエンコード変換スクリプト',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
使用例:
  python convert_encoding.py sample.txt                 # SJIS→UTF-8、新規ファイル作成
  python convert_encoding.py ./text_files/              # フォルダ内の全txtファイルとmdファイルを変換
  python convert_encoding.py sample.txt -r              # UTF-8→SJIS変換
  python convert_encoding.py sample.txt -o              # 元ファイルを上書き
  python convert_encoding.py sample.txt -r -o           # UTF-8→SJIS、上書き
  python convert_encoding.py ./mixed_files/ -q          # 静寂モード(エラーメッセージ非表示)
        """
    )
    
    parser.add_argument(
        'path',
        help='変換対象のファイルまたはフォルダパス'
    )
    
    parser.add_argument(
        '-r', '--reverse',
        action='store_true',
        help='UTF-8→SJIS変換(省略時はSJIS→UTF-8)'
    )
    
    parser.add_argument(
        '-o', '--overwrite',
        action='store_true',
        help='元ファイルを上書き(省略時は新規ファイル作成)'
    )
    
    parser.add_argument(
        '-q', '--quiet',
        action='store_true',
        help='エラーメッセージと警告メッセージを非表示にする'
    )
    
    # 引数の解析
    args = parser.parse_args()
    
    target_path = args.path
    reverse_mode = args.reverse
    overwrite_mode = args.overwrite
    quiet_mode = args.quiet
    
    # エンコーディングの設定
    if reverse_mode:
        from_encoding = 'utf-8'
        to_encoding = 'shift_jis'
        if not quiet_mode:
            print("変換モード: UTF-8 → SJIS")
    else:
        from_encoding = 'shift_jis'
        to_encoding = 'utf-8'
        if not quiet_mode:
            print("変換モード: SJIS → UTF-8")
    
    # ファイル処理モードの表示
    if not quiet_mode:
        if overwrite_mode:
            print("ファイル処理: 元ファイルを上書き")
        else:
            print("ファイル処理: 新規ファイルを作成")
        
        if quiet_mode:
            print("出力モード: 静寂モード(エラーメッセージ非表示)")
        
        print("-" * 50)
    
    # 対象ファイルの取得
    target_files = get_target_files(target_path, quiet_mode)
    if not target_files:
        if not quiet_mode:
            print("変換対象のファイルがありません。")
        sys.exit(1)
    
    # ファイルの変換処理
    success_count = 0
    error_count = 0
    
    for target_file in target_files:
        if not quiet_mode:
            print(f"\n処理中: {target_file}")
        
        # エンコーディング自動検出(参考情報として表示)
        if not quiet_mode:
            detected_encoding = detect_encoding(target_file)
            if detected_encoding:
                print(f"  検出されたエンコーディング: {detected_encoding}")
                
                # 検出されたエンコーディングが変換元と異なる場合は警告
                if detected_encoding.lower() not in [from_encoding.lower(), 'cp932'] and from_encoding.lower() == 'shift_jis':
                    print(f"  警告: 検出されたエンコーディング({detected_encoding})が想定と異なります")
                elif detected_encoding.lower() != from_encoding.lower() and from_encoding.lower() == 'utf-8':
                    print(f"  警告: 検出されたエンコーディング({detected_encoding})が想定と異なります")
        
        # ファイル変換の実行
        if convert_file_encoding(target_file, from_encoding, to_encoding, overwrite_mode, quiet_mode):
            success_count += 1
        else:
            error_count += 1
    
    # 結果の表示
    if not quiet_mode:
        print("\n" + "=" * 50)
        print(f"変換結果: 成功 {success_count}件, エラー {error_count}件")
        print("処理が完了しました。")


if __name__ == "__main__":
    main()
  

インストールと使い方

準備

準備は簡単。以下の2ステップだけです。

  1. 上記のスクリプトをファイルに保存します。この記事ではconvert_encoding.pyという名前で保存したとします。なお、ファイル名自体は任意ですが、拡張子は .py にしてください。
  2. ターミナル(コマンドプロンプトやPowerShell)で、上記のPythonスクリプトをファイルとして保存したフォルダに移動します。

これだけで準備完了です。

基本的な使い方

使い方は非常にシンプルです。

python convert_encoding.py 変換したいファイル or フォルダのパス オプション

上記のように、pythonコマンドの後にスクリプトのファイル名と変換したいファイルやフォルダのパスを指定します。必要に応じてオプションを追加します。簡単!

例1: 単一ファイルをSJISからUTF-8へ変換

sample.txt というSJISのファイルをUTF-8に変換してみます。

python convert_encoding.py sample.txt

これを実行すると、デフォルトでは元のファイルはそのまま残り、sample_utf-8.txt という名前で新しいファイルが作成されます。

例2: フォルダ内のファイルを一括変換

documents というフォルダの中にある .txt.md ファイルをすべて変換したい場合は、フォルダのパスを指定します。

python convert_encoding.py ./documents/

フォルダ内の対象ファイルがそれぞれ 〜_utf-8.txt のような名前で保存されます。元ファイルはそのまま残ります。

例3: UTF-8からSJISへ逆変換

逆方向の変換、つまりUTF-8からSJISへの変換をしたい場合は -r (--reverse) オプションを付けます。

python convert_encoding.py note.md -r

これで note_sjis.md というSJISのファイルが生成されます。

オプション機能

もう少し細かい制御ができるように、いくつかのオプションを用意しました。

オプション 短縮形 説明
--reverse -r UTF-8 → SJISに変換します。(デフォルトはSJIS → UTF-8)
--overwrite -o 元のファイルを直接上書きします。(バックアップ推奨!)
--quiet -q 処理メッセージを非表示にします。(自動化スクリプトに組み込む際に便利)

上書きモード (-o)

新しいファイルを作らずに、元のファイルを直接変更したい場合は -o オプションを使います。

# sample.txtを直接UTF-8に変換して上書き
python convert_encoding.py sample.txt -o

# UTF-8からSJISへの変換と上書きを組み合わせる
python convert_encoding.py report.txt -r -o

このオプションを使うと元ファイルが完全に置き換えられてしまうので、大事なファイルの場合は事前にバックアップを取ることを強くお勧めします。

静寂モード (-q)

フォルダ内に .txt.md 以外のファイルがたくさんあると、「〜をスキップしました」という警告がたくさん出てきます。そういったメッセージを非表示にしたい場合は -q オプションを指定します。

# メッセージを何も表示せず、静かに処理を実行
python convert_encoding.py ./mixed_folder/ -q

これで一切のメッセージが表示されなくなります。そのため、エラーが起きても気づかない可能性があるので注意してください。どちらかというと、自動化スクリプトの中で使う場合に便利なオプションですね。

動作の裏側(少しだけ技術的な話)

このスクリプトがどう動いているか、少しだけ解説します。

エンコーディングの自動検出

ファイルの中身を読んで、どのエンコーディングで書かれているかを判別するのは、実は結構メンドクサイ悩ましい問題です。このスクリプトでは、以下の順番でデコードを試みて、成功したものをそのファイルのエンコーディングと判断しています。

  1. UTF-8
  2. Shift-JIS (shift_jis)
  3. CP932 (Windowsで使われるShift_JISの亜種)
  4. EUC-JP
  5. ISO-2022-JP

短い文章だと誤認識することもありますが、一般的なテキストファイルなら、だいたいはこれでうまく判別してくれます。

対象ファイルのフィルタリング

フォルダを指定した場合、中にあるすべてのファイルを変換するわけではありません。誤って画像ファイルなどを壊してしまわないように、拡張子が .txt.md のファイルのみを処理対象としています。それ以外のファイルは自動でスキップされるので安心です。

まとめ

ということで、今回は文字コードの変換を楽にするための自作Pythonスクリプトを紹介しました。

WindowsとMac/Linuxの間でファイルをやり取りする機会が多い方や、古いシステムのテキストデータを扱う必要がある方にとって、少しでも役に立てば幸いです。

comments powered by Disqus