試験運用中なLinux備忘録・旧記事

はてなダイアリーで公開していた2007年5月-2015年3月の記事を保存しています。

PythonでファイルサイズとMD5/SHA-1ダイジェストの計算結果を整形して出力

などの記事の中で、ファイルの

  • 名前
  • サイズ(バイト単位・3桁ごとにコンマを付ける)
  • MD5SHA-1のダイジェスト(ハッシュ)

を情報として貼り付けているところがあるのだが、テーブルのHTMLソースを手書きして、値もそれぞれコマンドの結果から手動で貼り付けたりしていたため、作成するのが面倒だった。
そこで、ファイルの場所を引数に指定するだけで情報を簡単に出力できるようなPythonスクリプトを作成してみた。

  1. コード
  2. Pythonのメモ
    1. MD5とSHA-1
    2. 数値に3桁区切りのコンマを付ける
    3. 標準エラー出力へのメッセージ出力
    4. ファイルサイズの取得
    5. ファイルの場所などに関して

コード

#! /usr/bin/python
# -*- coding: utf-8 -*-

from __future__ import print_function

import os
import sys
import md5
import sha
import locale

class Digest:
  md5 = None
  sha1 = None

def calc_digest (f, hashalgo, calcfunc):
  """
  ファイルを開き、ダイジェスト計算用関数を呼び出してDigestクラスのメンバに格納
  f       : 開かれたファイルオブジェクト
  hashalgo: ハッシュアルゴリズムの文字列(「md5」か「sha1」)
  calcfunc: ダイジェスト計算の関数(「hashlib.md5」か「hashlib.sha1」)
  """
  exec ('Digest.{0} = "{1}"'.format (hashalgo, calcfunc (f.read ()).hexdigest ()))


# メイン処理
fileslist = sys.argv[1:]

# 引数が足りない場合に使用法を表示
if not fileslist:
  sys.exit ('使用法: {0} [ファイル...]\nファイル情報をテーブルに表示します'.format (__file__))

# 3桁ごとのコンマ付けにロケール指定が必要
locale.setlocale (locale.LC_ALL, '')

# 出力結果文字列
results = []

# 各ファイルごとに処理
for infile in fileslist:

  # ファイルを開いてダイジェスト計算
  try:
    f = open (infile, 'rb')
    calc_digest (f, 'md5', md5.new)
    os.lseek (f.fileno (), 0, 0)  # ファイルの先頭に移動する必要がある
    calc_digest (f, 'sha1', sha.new)
    f.close ()
  except IOError as e:
    print ('エラー: ファイルの読み書きに失敗しました: {0}'.format (infile), file=sys.stderr)
    continue  # 開けなかったら次のファイルへ

  # ファイルサイズを取得する
  try:
    info = os.stat (infile)
  except OSError as e:
    print ('エラー: ファイル情報が参照できません: {0}'.format (infile), file=sys.stderr)
    continue

  # 結果を整形して表示
  results.append ('-' * 80)
  results.append ('''<table>
<caption>ファイル情報: {0}</caption>
<thead>
<tr><th>項目</th><th>値</th></tr>
</thead>
<tbody>
<tr><td>ファイルサイズ</td><td>{1}バイト</td></tr>
<tr><td>MD5</td><td>{2}</td></tr>
<tr><td>SHA-1</td><td>{3}</td></tr>
</tbody>
</table>'''.format (os.path.basename (infile),
                    locale.format ('%d', info.st_size, True),
                    Digest.md5,
                    Digest.sha1))

# 全てエラーの場合は末尾の区切り線は出力しない
if Digest.md5 and Digest.sha1:
  results.append ('-' * 80)

# 結果を出力
for r in results:
  print (r)

(2009/5/28)IOErrorのtry節の中身を修正
(2014/11/20)細かい部分を色々と修正
下は使用例。

$ [スクリプトの場所] Python-Docs-2.4/tut.pdf Python-Docs-2.4/ref.pdf
--------------------------------------------------------------------------------
<table>
<caption>ファイル情報: tut.pdf</caption>
<thead>
<tr><th>項目</th><th>値</th></tr>
</thead>
<tbody>
<tr><td>ファイルサイズ</td><td>864,070バイト</td></tr>
<tr><td>MD5</td><td>37f81c81d690b0c678486f6367f6e752</td></tr>
<tr><td>SHA-1</td><td>373f56f55b4cc59af2191f816942c5b52e5b5c6b</td></tr>
</tbody>
</table>
--------------------------------------------------------------------------------
<table>
<caption>ファイル情報: ref.pdf</caption>
<thead>
<tr><th>項目</th><th>値</th></tr>
</thead>
<tbody>
<tr><td>ファイルサイズ</td><td>1,028,435バイト</td></tr>
<tr><td>MD5</td><td>9cf16f2eb5df417ceacfb7f047c7039c</td></tr>
<tr><td>SHA-1</td><td>c603b4177b8e149fbc64178d54613ae2b406b8c6</td></tr>
</tbody>
</table>
--------------------------------------------------------------------------------

(2008/6/22)引数の処理を修正

Pythonのメモ

MD5SHA-1
これらに関しては、ファイルオブジェクトを渡すだけで簡単にダイジェストを計算して出力できる機能がPythonにある。

いずれも使い方は簡単で、「import md5」と「import sha」で各モジュールを読み込む指定をした後で

md5.new([ファイルオブジェクト].read()).hexdigest()
sha.new([ファイルオブジェクト].read()).hexdigest()

により16進形式のダイジェスト文字列がそれぞれ得られる。ただし、同じファイルオブジェクトを続けて渡すと、2つ目の関数が無効な値を返すため、

os.lseek([ファイルオブジェクト].fileno(), 0, 0)

を呼び出し、「ファイル記述子の操作」にあるos.lseek()関数により、先頭を指すようにする必要がある。

数値に3桁区切りのコンマを付ける
3桁ごとに「,」を入れて数値を表示することは比較的容易にでき、標準のモジュール(locale)で実現できる。
今回のコードでは、ロケール名について、環境変数を使用する形で決定するようにした。

標準エラー出力へのメッセージ出力
Python 2では「import sys」を書いてから

print >> sys.stderr, '[メッセージ文字列]'

の形で出力する。
(2014/11/20)Python 3もしくは「from __future__ import print_function」を記述したPython 2.6以上では

print ('[メッセージ文字列]', file=sys.stderr)

とする。

ファイルサイズの取得
http://docs.python.jp/3/library/os.html#files-and-directories
に書かれているos.stat()により各種情報を取得する。オブジェクトが返され、メンバ変数「st_size」がバイト単位のサイズとなっているため、「[statオブジェクト].st_size」を取り出せばOK。

ファイルの場所などに関して

パス名に関する便利な関数は
http://docs.python.jp/3/library/os.path.html
を参照。

参考URL:

*1:「/」で終わる文字列が与えられたときの挙動がコマンドのbasenameと若干異なるので注意