PythonでLZHファイルを展開してみる

2022.05.10

はじめに

データアナリティクス事業本部のkobayashiです。

今は昔アーカイバとして頻繁に使われていましたLZHファイルですが、脆弱性の問題から使用が非推奨となり現在ではほぼ使用されていないです。ただ今回LZHのファイルをPythonで展開する機会がありPythonでLZHファイルを扱う方法を調べましたのでその内容をまとめます。

環境

  • macOS 10.15.7
  • Python 3.7.12

PythonでのLZH解凍方法

LZHは現在ほぼ使われていない圧縮形式のため展開する手段もあまり見つからなかったので当初はlhaをインストールしてsubprocessから使う方法を考えていましたが継続して調査していたところ Lhafile というPythonのモジュールが一つだけ見つかりました。

上記の様な経緯から以下の二つの方法を紹介したいと思います。

  • subprocessからlhaを呼び出す
  • Lhafileモジュールで操作する

なお今回扱うLZHファイルの中身ですが以下の様な郵便番号のCSVファイルが圧縮されているファイルとなります。

zipcode.lzh
├── 08IBARAK.CSV
├── 09TOCHIG.CSV
├── 10GUMMA.CSV
├── 11SAITAM.CSV
├── 12CHIBA.CSV
├── 13TOKYO.CSV
└── 14KANAGA.CSV

subprocessからlhaを呼び出す

Pythonのsubprocessを使う方法は弊社ブログに詳しい記事がありますのでそちらをご確認ください。

subprocessからlhaを呼び出す方法ですが、lhaをインストールする必要がありますのでbrewではじめにインストールしておきます。またバージョンとオプションも確認しておきます。

$ brew install lha
$ lha --version
LHa for UNIX version 1.14i-ac20050924p1 (i686-apple-darwin19.6.0)
$ lha --help 
LHarc    for UNIX  V 1.02  Copyright(C) 1989  Y.Tagawa
LHx      for MSDOS V C2.01 Copyright(C) 1990  H.Yoshizaki
LHx(arc) for OSK   V 2.01  Modified     1990  Momozou
LHa      for UNIX  V 1.00  Copyright(C) 1992  Masaru Oki
LHa      for UNIX  V 1.14  Modified     1995  Nobutaka Watazaki
LHa      for UNIX  V 1.14i Modified     2000  Tsugio Okamoto
                   Autoconfiscated 2001-2005  Koji Arai
usage: lha [-]<commands>[<options>] [-<options> ...] archive_file [file...]
  commands:  [axelvudmcpt]
  options:   [q[012]vnfto[567]dizg012e[w=<dir>|x=<pattern>]]
  long options: --system-kanji-code={euc,sjis,utf8,cap}
                --archive-kanji-code={euc,sjis,utf8,cap}
                --extract-broken-archive
                --help
                --version
commands:                           options:
 a   Add(or replace) to archive      q{num} quiet (num:quiet mode)
 x,e EXtract from archive            v  verbose
 l,v List / Verbose List             n  not execute
 u   Update newer files to archive   f  force (over write at extract)
 d   Delete from archive             t  FILES are TEXT file
 m   Move to archive (means 'ad')    o[567] compression method (a/u/c)
 c   re-Construct new archive        d  delete FILES after (a/u/c)
 p   Print to STDOUT from archive    i  ignore directory path (x/e)
 t   Test file CRC in archive        z  files not compress (a/u/c)
                                     g  Generic format (for compatibility)
                                        or not convert case when extracting
                                     0/1/2 header level (a/u/c)
                                     e  TEXT code convert from/to EUC
                                     w=<dir> specify extract directory (x/e)
                                     x=<pattern>  eXclude files (a/u/c)

PythonでLZHを展開したいのですが、その際に「上書き」と「サブディレクトリに展開」を行いたいので以下のコマンドを使います。

$ lha x -f -w={サブディレクトリ名} {展開するLZHファイル名}

これをsubprocessで呼び出すPythonスクリプトを書くと以下の様になります。

後々にPythonスクリプトでエラーハンドリング等を行いため標準出力、標準エラー出力を取得できるようにしてあります。

import subprocess
from subprocess import PIPE

extract_dir = "zipcodes" # サブディレクトリ名
target_file = "zipcode.lzh" # 展開するLZHファイル名
proc = subprocess.run(
    "lha x -f -w={} {}".format(extract_dir, target_file),
    shell=True,
    stdout=PIPE,
    stderr=PIPE,
    text=True,
)

print("returncode: {}".format(proc.returncode))
print("stdout: {}".format(proc.stdout))
print("stderr: {}".format(proc.stderr))

実行結果

returncode: 0
stdout: 
zipcodes/08IBARAK.CSV   - Melting  :  .........................
zipcodes/08IBARAK.CSV   - Melting  :  ooooooooooooooooooooooooo
zipcodes/08IBARAK.CSV   - Melted  

zipcodes/09TOCHIG.CSV   - Melting  :  ................................
zipcodes/09TOCHIG.CSV   - Melting  :  oooooooooooooooooooooooooooooooo
zipcodes/09TOCHIG.CSV   - Melted  

zipcodes/10GUMMA.CSV    - Melting  :  ............................
zipcodes/10GUMMA.CSV    - Melting  :  oooooooooooooooooooooooooooo
zipcodes/10GUMMA.CSV    - Melted  

zipcodes/11SAITAM.CSV   - Melting  :  ..........................
zipcodes/11SAITAM.CSV   - Melting  :  oooooooooooooooooooooooooo
zipcodes/11SAITAM.CSV   - Melted  

zipcodes/12CHIBA.CSV    - Melting  :  ...............................
zipcodes/12CHIBA.CSV    - Melting  :  ooooooooooooooooooooooooooooooo
zipcodes/12CHIBA.CSV    - Melted  

zipcodes/13TOKYO.CSV    - Melting  :  ................................
zipcodes/13TOKYO.CSV    - Melting  :  oooooooooooooooooooooooooooooooo
zipcodes/13TOKYO.CSV    - Melted  

zipcodes/14KANAGA.CSV   - Melting  :  .......................
zipcodes/14KANAGA.CSV   - Melting  :  ooooooooooooooooooooooo
zipcodes/14KANAGA.CSV   - Melted  

stderr:

Lhafileモジュールで操作する

次にPythonモジュールの Lhafile を使ってみます。

GitHub - FrodeSolheim/python-lhafile: LHA archive support for Python

$ pip install lhafile
$ pip list |grep lhafile
lhafile                        0.3.0

Lhafileの使い方は、lhafile.Lhafile()でLZHファイルを読み込みます。あとは組み込み関数のopen()と同様にf.read()でLZHファイル内のファイルを読み込んで操作します。

import lhafile
import os

f = lhafile.Lhafile("zipcode.lzh")

extract_dir = "zipcodes" # サブディレクトリ名
os.makedirs(extract_dir, exist_ok=True)
for info in f.infolist():
    fname = info.filename
    with open("{}/{}".format(extract_dir, fname), "wb") as tf:
        tf.write(f.read(info.filename))

上記のスクリプトを実行した結果LZHファイルの中身がzipcodesディレクトリに展開されます。

zipcodes/
├── 08IBARAK.CSV
├── 09TOCHIG.CSV
├── 10GUMMA.CSV
├── 11SAITAM.CSV
├── 12CHIBA.CSV
├── 13TOKYO.CSV
└── 14KANAGA.CSV

また応用的な使い方として、展開されたファイルの中身を直接pandas.DataFrameとして扱うこともできます。

import lhafile
import pandas as pd
from io import BytesIO

f = lhafile.Lhafile("zipcode.lzh")

for info in f.infolist():
    fname = info.filename
    df = pd.read_csv(BytesIO(f.read(fname)))
    print(df)

実行結果

      14101  230    2300000  カナガワケン      ヨコハマシツルミク  ... 0.1 0.2 0.3 0.4  0.5
0     14101    230  2300033  カナガワケン      ヨコハマシツルミク  ...   0   1   0   0    0
1     14101    230  2300035  カナガワケン      ヨコハマシツルミク  ...   0   1   0   0    0
2     14101    230  2300021  カナガワケン      ヨコハマシツルミク  ...   0   0   0   0    0
3     14101    230  2300024  カナガワケン      ヨコハマシツルミク  ...   0   0   0   0    0
4     14101    230  2300022  カナガワケン      ヨコハマシツルミク  ...   0   0   0   0    0
...     ...    ...      ...      ...            ...  ...  ..  ..  ..  ..  ...
2293  14401  24303  2430308  カナガワケン  アイコウグンアイカワマチ  ...   0   0   0   0    0
2294  14402  24301  2430100  カナガワケン  アイコウグンキヨカワムラ  ...   0   0   0   0    0
2295  14402    257  2570061  カナガワケン  アイコウグンキヨカワムラ  ...   0   0   0   0    0
2296  14402  24301  2430112  カナガワケン  アイコウグンキヨカワムラ  ...   0   0   0   0    0
2297  14402  24301  2430111  カナガワケン  アイコウグンキヨカワムラ  ...   0   0   0   0    0

[2298 rows x 15 columns]

まとめ

今ではほぼ見なくなったLZHで圧縮されたファイルをPythonで展開してみました。需要は少ないと思いますがどなたかの参考になれば幸いです。

最後まで読んで頂いてありがとうございました。