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 split
やpython 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()
おわりに
今回はバイナリファイルの分割/結合を見てみました。
バイナリファイルを分割できるようになると、大きなファイルを複数のファイルにして管理したりすることができます。
🦝 < バイナリファイルがおっきくなっちゃった
🐭 < 分割や!