ユーニックス総合研究所

  • home
  • archives
  • python-split-binary-file

Pythonでバイナリファイルを分割・結合する

  • 作成日: 2021-04-22
  • 更新日: 2023-12-24
  • カテゴリ: Python

Pythonでバイナリファイルを分割する

Pythonではバイナリファイルを扱うことができます。
大きなバイナリファイルは分割しておきたくなる時があります。

この記事では1つのバイナリファイルを複数のバイナリファイルに分割する方法と、それらを再び1つのファイルに結合する方法を解説します。
具体的には↓を見ていきます。

  • ソースコード全文
  • スクリプトの実行結果
  • バイナリファイルの分割方法
  • バイナリファイルの結合方法
  • ソースコードの解説

ソースコード全文

今回はスクリプトとしてコードを書きました。
以下がスクリプトの全文です。

"""  
バイナリファイルを分割/結合するスクリプト  

License: MIT  
Created at: 2021/03/11  
"""  
import os  
import sys  


def split_bin_file(srcfname, chunk, outdir):  
    """  
    srcfnameのバイナリファイルをchunkのサイズで分割し、outdir以下に分割したファイルを作成する  
    """  
    with open(srcfname, 'rb') as fin:  # バイナリファイルとして開く  
        split_bin_stream(fin, srcfname, chunk, outdir)  # 処理を委譲  


def split_bin_stream(fin, srcfname, chunk, outdir):  
    """  
    finのストリームをchunkのサイズで分割し、outdir以下にsrcfnameの連番で分割したファイルを作成する  
    """  
    if not os.path.exists(outdir):  # ディレクトリが存在しないなら  
        os.mkdir(outdir)  # 作成  

    i = 0  # 連番  
    while True:  
        data = fin.read(chunk)  # chunkサイズだけ読み込む  
        if not len(data):  # 読み込みが空ならループから抜ける  
            break  

        outfname = f'{srcfname}.{i}'  # ファイル名を合成  
        outpath = os.path.join(outdir, outfname)  # パスを作成  
        write_data(outpath, data)  # データを書き込む  

        i += 1  # 連番のインクリメント  


def write_data(path, data):  
    """  
    pathをバイナリファイルとして開きdataを書き込む  
    """  
    with open(path, 'wb') as fout:  
        fout.write(data)  


def join_bin_files(dstfname, files):  
    """  
    dstfnameのバイナリファイルににfilesのファイル群を結合して保存する  
    """  
    with open(dstfname, 'wb') as fout:  
        for file in files:  
            with open(file, 'rb') as fin:  
                fout.write(fin.read())  


def main():  
    """  
    コマンドで分割/結合を分岐する(テスト用)  

    使用例:  
        python split.py split  
        python split.py join  
    """  
    if len(sys.argv) < 2:  
        print('python split.py [split|join]')  
        sys.exit(0)  

    cmd = sys.argv[1]  
    if cmd == 'split':  
        split_bin_file('file.dat', 4, 'out')  
    elif cmd == 'join':  
        join_bin_files('join.dat', [  
            'out/file.dat.0',  
            'out/file.dat.1',  
            'out/file.dat.2',  
        ])  


if __name__ == '__main__':  
    main()  

スクリプトの実行結果

前述のスクリプトを実行します。

まず分割のテストです。
分割対象のファイル(file.dat)は↓のような内容です。

1234abcdABCD  

以下のコマンドを実行すると、out/ディレクトリ以下に分割したファイルを作成します。

$ python split.py split  

out/ディレクトリ以下を見るとファイルが作成されているのがわかります。

$ ls out/  
file.dat.0  file.dat.1  file.dat.2  

file.dat.0, file.dat.1, file.dat.2の内容はそれぞれ↓のようになっています。

1234  
abcd  
ABCD  

これらの分割したファイルを再び1つのファイルに結合するには↓のコマンドを実行します。

$ python split.py join  

↑のコマンドを実行するとjoin.datというファイルが作成されます。
join.datの中身は↓のようになっています。

1234abcdABCD  

分割したファイルから元のファイルを復元できているのがわかります。

バイナリファイルの分割方法

今回のバイナリファイルの分割でキモとなるのがread()の扱い方です。
その前にバイナリファイルの開き方ですが、バイナリファイルは組み込み関数のopen()で開きます。
open()の第2引数にオープンモードrbを指定すると読み込み用のバイナリファイルを開くことができます。

fin = open('file.dat', 'rb')  
fin.close()  

with文を使うとファイルのクローズを自動化できますが、with文を使う場合は↓のようになります。

with open('file.dat', 'rb') as fin:  
    pass  

このときfinはファイルオブジェクトまたはストリームと呼ばれます。
このファイルオブジェクトのメソッドread()を使うと、ファイルからデータを読み込むことができます。
このときread()の第1引数に読み込みたいバイト数を指定すると、そのバイト数だけ読み込むことができます。

つまり分割後のファイル群の1つのファイルの大きさを決めておき、そのサイズ(バイト数)をread()に渡せば1つのファイル分のデータを読み込めるということになります。

data = fin.read(4)  # 4バイト分のデータを読み込む  

上記が分割のキモとなる部分です。

バイナリファイルの結合方法

分割されたファイル群を1つずつ読み込んで、そのデータを1つのファイルに書き込んでいけば元のファイルを復元することができます。

ソースコードの解説

ソースコードの解説になります。

必要モジュールのインポート

今回インポートするモジュールはほとんど分割のロジックとは関係ありません。
osはディレクトリの作成に使い、sysはコマンドライン引数のチェックに使っています。

import os  
import sys  

split_bin_file()でファイルを分割する

split_bin_file()は引数srcfnameのファイルを開き、その中身のデータを複数のファイルに分割します。
分割する大きさは引数chunkで決まります。
引数outdirには分割先のディレクトリを指定します。

内容的にはファイルを開いた後にファイルオブジェクト(ストリーム)をsplit_bin_stream()に渡して処理を委譲しています。

def split_bin_file(srcfname, chunk, outdir):  
    """  
    srcfnameのバイナリファイルをchunkのサイズで分割し、outdir以下に分割したファイルを作成する  
    """  
    with open(srcfname, 'rb') as fin:  # バイナリファイルとして開く  
        split_bin_stream(fin, srcfname, chunk, outdir)  # 処理を委譲  

split_bin_stream()でストリームを分割する

実際の分割処理はこのsplit_bin_stream()で行っています。
split_bin_stream()は引数fin(ファイルオブジェクト)を引数chunkのサイズに分割し、引数srcfnameのファイル名を連番にして引数outdirのディレクトリ以下に保存します。

最初にos.path.exists()でディレクトリの存在を確認し、存在しなければos.mkdir()でディレクトリを作成しています。

それから無限ループに入りfin.read(chunk)でファイルオブジェクトからchunkサイズ分のデータを読み込みます。
そのデータを合成した連番のファイル名のファイルに書き込んでいます。

def split_bin_stream(fin, srcfname, chunk, outdir):  
    """  
    finのストリームをchunkのサイズで分割し、outdir以下にsrcfnameの連番で分割したファイルを作成する  
    """  
    if not os.path.exists(outdir):  # ディレクトリが存在しないなら  
        os.mkdir(outdir)  # 作成  

    i = 0  # 連番  
    while True:  
        data = fin.read(chunk)  # chunkサイズだけ読み込む  
        if not len(data):  # 読み込みが空ならループから抜ける  
            break  

        outfname = f'{srcfname}.{i}'  # ファイル名を合成  
        outpath = os.path.join(outdir, outfname)  # パスを作成  
        write_data(outpath, data)  # データを書き込む  

        i += 1  # 連番のインクリメント  

write_data()でデータを書き込む

write_data()は引数pathをバイナリファイルとして開いて引数dataを書き込みます。
書き込みはfout.write(data)で行っています。

def write_data(path, data):  
    """  
    pathをバイナリファイルとして開きdataを書き込む  
    """  
    with open(path, 'wb') as fout:  
        fout.write(data)  

join_bin_files()で連番のファイルを結合する

join_bin_files()は引数dstfnameのファイルに引数filesの連番のファイル群の中身を読み込んで保存します。
やってることはfilesのファイルを開いてデータを読み込み、dstfnameのファイルにデータを書き込んでいるだけです。

def join_bin_files(dstfname, files):  
    """  
    dstfnameのバイナリファイルににfilesのファイル群を結合して保存する  
    """  
    with open(dstfname, 'wb') as fout:  
        for file in files:  
            with open(file, 'rb') as fin:  
                fout.write(fin.read())  

main()でテストする

main()はテスト用の関数です。
このスクリプトをsplit.pyなどに保存し、コマンドラインからpython split.py splitpython split.py joinなどのコマンドを実行すると、テストを実行できます。
テストとは言っても結果の確認は目視で行います。

この関数が実行されるとsplitコマンドでは現在のディレクトリのoutディレクトリ以下にfile.datを分割したファイルが作成されます。
joinコマンドの場合はout/以下のファイルを結合してjoin.datというファイルが作成されます。

def main():  
    """  
    コマンドで分割/結合を分岐する(テスト用)  

    使用例:  
        python split.py split  
        python split.py join  
    """  
    if len(sys.argv) < 2:  
        print('python split.py [split|join]')  
        sys.exit(0)  

    cmd = sys.argv[1]  
    if cmd == 'split':  
        split_bin_file('file.dat', 4, 'out')  
    elif cmd == 'join':  
        join_bin_files('join.dat', [  
            'out/file.dat.0',  
            'out/file.dat.1',  
            'out/file.dat.2',  
        ])  


if __name__ == '__main__':  
    main()  

おわりに

今回はバイナリファイルの分割/結合を見てみました。
バイナリファイルを分割できるようになると、大きなファイルを複数のファイルにして管理したりすることができます。

🦝 < バイナリファイルがおっきくなっちゃった

🐭 < 分割や!