■ md5sum コマンド

Linux などで使われている md5sum コマンドです。 MD5 を利用したチェックサムを確認するコマンドです。

■ 使い方

ファイルのチェックサムを確認します。 引数に確認したいファイル名を渡した場合は次のような結果になります。

$ ll *.rb
-rw-r--r--   1 tetsu    staff        2677 May  3  2001 instruby.rb
-rw-r--r--   1 tetsu    staff        3487 Mar 21 04:48 mkconfig.rb
-rw-r--r--   1 tetsu    staff         978 May  8  2001 rubytest.rb
$ md5sum.rb *.rb
3f38e2d8ac1efbe5e4b530e2ecd8feb9  instruby.rb
a726fa8939baddd0cdb227480d387edc  mkconfig.rb
82336e066b58c56c368c3490ccff11f9  rubytest.rb

一度チェックサムを計算しておくと、あとで変更されているか確認可能です。 Ruby のバイナリで確認してみます。 2002/5/1 のバイナリと 2002/5/4 のバイナリの比較です。

$ pwd
/t/src/ruby/1.7/2002/05/01
$ ll ruby-1.7.2 libruby-1.7.2.a
-rw-r--r--   1 tetsu    staff      912262 May  1 22:48 libruby-1.7.2.a
-rwxr-xr-x   1 tetsu    staff      585488 May  1 22:49 ruby-1.7.2*
$ md5sum.rb ruby-1.7.2 libruby-1.7.2.a > /tmp/j.txt
$ md5sum.rb -c /tmp/j.txt
ruby-1.7.2: OK
libruby-1.7.2.a: OK

$ cd ../04
$ pwd
/t/src/ruby/1.7/2002/05/04
$ ll ruby-1.7.2 libruby-1.7.2.a
-rw-r--r--   1 tetsu    staff      911966 May  5 02:17 libruby-1.7.2.a
-rwxr-xr-x   1 tetsu    staff      585296 May  5 02:18 ruby-1.7.2*
$ md5sum.rb -c /tmp/j.txt
ruby-1.7.2: FAILED
libruby-1.7.2.a: FAILED
/home/tetsu/bin/md5sum.rb: WARNING: 2 of 2 computed checksum did NOT match
zsh: 5180 exit 1     md5sum.rb -c /tmp/j.txt
$ md5sum.rb -c --status /tmp/j.txt
zsh: 5185 exit 1     md5sum.rb -c --status /tmp/j.txt
-c オプションを指定した場合、チェックサムが一致しない場合は、ファイル単位にレポートします。 また、同時に --status オプションをしていすると、終了値だけになります。 ここで使用しているシェルは zsh です。 zsh は、終了ステータスをその場で確認可能です。 他のシェルを使っている場合は、終了ステータスは明示的に確認しないとわからないので注意ください(シェル変数 $?, $status)。

オプション意味
-c, --checkチェックサムの結果からファイルを確認します
--statusチェックサム確認のステータスだけ返します --check オプションと合わせて使用します

■ ソースコード

Web ブラウザでの表示上は Ruby のスクリプトですが、HTML の特殊文字をエスケープしています。 テキストとしてセーブしてご利用ください。

#! /usr/local/bin/ruby
# /home/tetsu/src/ruby/toolbox/md5sum.rb
# Created: January 28,1999 Thursday 12:42:33
# Author: tetsu(WATANABE Tetsuya)
# $Id: md5sum.rb,v 1.4 2007/05/07 06:29:14 tetsu Exp $
# usage: 
# hint: ruby-list 11775 11780

require 'md5'

def md5sum(str)
  md5 = MD5.new
  md5.update(str)
  md5.hexdigest
end

# close したいだけ close しなくてもいいかな
# File.open(filename).read で代用化
def fileread(file)
  f = File.open(file)
  str = f.read
  f.close
  str
end

def usage
  STDERR.puts "usage: #{$0} [OPTION] [FILE]...
  -c, --check      check MD5 sums against given list
      --status     do not output anything, status code shows success"
  exit 1
end

opt_check = false
opt_status = false

while ARGV[0] =~ /^-/
  $_ = ARGV.shift
  if ~/^-c/ or ~/^--check/
    opt_check = true
  elsif ~/^--status/
    opt_status = true
  else
    usage
  end
end

if opt_status == true and opt_check == false
  STDERR.puts "#{$0}: the --status option is meaningful only when verifying checksums(--check)"
  exit 1
end

if opt_check
  ck_count_total = 0
  ck_count_failed = 0
end

while file_str = gets(nil)
  if opt_check
    file_str.split("\n").each do |l|
      ck_count_total += 1
      sum1, fname = l.split
      sum2 = md5sum(fileread(fname))
      status = if sum1 == sum2
                 'OK'
               else
                 ck_count_failed += 1
                 'FAILED'
               end
      if opt_status
      else
        puts fname + ': ' + status
      end
    end
  else
    puts md5sum(file_str) + '  ' + ARGF.filename
  end
end

if opt_check and ck_count_failed > 0
  if opt_status
  else
    STDERR.puts "#{$0}: WARNING: #{ck_count_failed} of #{ck_count_total} computed checksum did NOT match"
  end
  exit 1
end

■ 解説

今回、対象となる「ファイル名」をつぎつぎ引数として処理を行なうのではなく、ARGF を利用しています。 これは、引数にファイルがなく標準入力が対象の場合に、記述する処理を共通化するのが楽だからです。 最初は、「ファイル名」を ARGV からとりだして、処理していました。 ファイルが対象の場合と標準入力が対象の場合に、どちらも共通の記述をうまく書くことができるので、私はこういう書き方がけっこう好きです。 標準入力とファイルの処理を明示的に記述すると、こういう感じでしょうか。

require 'md5'

def md5sum(f)
  md5 = MD5.new
  while mb = f.read(1024 * 1024)
    md5.update(mb)
  end
  md5.hexdigest
end

if ARGV.size == 0
  puts md5sum(STDIN) + '  -'
else
  while filename = ARGV.shift
    f = File.open(filename)
    puts md5sum(f) + '  ' + filename
    f.close
  end
end
--check オプションがないと、けっこう素直に書けるんですが? 処理をサブルーチンに分ける書きかたを基本にして書いてみました。 どちらのほうがわかりやすいですか? メインの処理の流れについては、こちらのほうがわかりやすいですね。 それと、ファイルを 1MB 単位に読んでいるので、サイズがめちゃくちゃ大きなファイルの場合、こちらのほうがいいでしょう。
#! /usr/local/bin/ruby
# /home/tetsu/src/ruby/toolbox/md5sum2.rb
# Created: May 06,2002 Monday 12:40:54
# Author: tetsu(WATANABE Tetsuya)
# $Id: md5sum2.rb,v 1.1 2002/05/06 03:41:13 tetsu Exp $
# usage:

require 'md5'

def check(f, opt_status)
  ck_count_total = 0
  ck_count_failed = 0

  while l = f.gets
    ck_count_total += 1
    sum1, filename = l.split
    r = File.open(filename)
    sum2 = md5sum(r)
    r.close
    status = if sum1 == sum2
               'OK'
             else
               ck_count_failed += 1
               'FAILED'
             end
    if opt_status
    else
      puts filename + ': ' + status
    end
  end

  if ck_count_failed > 0
    if opt_status
    else
      STDERR.puts "#{$0}: WARNING: #{ck_count_failed} of #{ck_count_total} computed checksum did NOT match"
    end
    return 1
  end
  0
end

def md5sum(f)
  md5 = MD5.new
  while mb = f.read(1024 * 1024)
    md5.update(mb)
  end
  md5.hexdigest
end

def usage
  STDERR.puts "usage: #{$0} [OPTION] [FILE]...
  -c, --check      check MD5 sums against given list
      --status     do not output anything, status code shows success"
  exit 1
end

opt_check = false
opt_status = false

while ARGV[0] =~ /^-/
  $_ = ARGV.shift
  if ~/^-c/ or ~/^--check/
    opt_check = true
  elsif ~/^--status/
    opt_status = true
  else
    usage
  end
end

if opt_status == true and opt_check == false
  STDERR.puts "#{$0}: the --status option is meaningful only when verifying checksums(--check)"
  exit 1
end

status = 0

if ARGV.size == 0
  if opt_check
    status = check(STDIN, opt_status)
  else
    puts md5sum(STDIN) + '  -'
  end
else
  while filename = ARGV.shift
    f = File.open(filename)
    if opt_check
      status = check(f, opt_status)
    else
      puts md5sum(f) + '  ' + filename
    end
    f.close
  end
end

exit status
やっぱり、こちらのほうがいいかも? メイン処理が読みやすいということは、流れがわかりやすいので大切ですしね。 とりあえずは、こういう書きかたがあるという比較サンプルとして使ってください。

一度チェックサムを計算した結果が入ったファイルから、ファイルの確認を行なう --check オプションの場合、サブルーチンにすることも考えました。 今回は、記述量がすくなかったので、そのまま書いてしまいましたが、明示的に分けてしまうのも処理の流れを読みやすくするという意味でいいと思います。

MD5 のチェックサム、以前 ruby-list 11775 11780 で話題になったのですが、一行で書くとこんな感じ。

$ ruby -r md5 -e 'puts MD5.new(open("ruby-1.7.2").read).hexdigest'
b9acecbcd93cbceb5104d8ae7ef6a873
$ md5sum.rb ruby-1.7.2
b9acecbcd93cbceb5104d8ae7ef6a873  ruby-1.7.2

システム管理的な作業としては、チェックサムをとっておくと、あとあと便利かも。

# find /sbin /usr/sbin /etc /usr/bin /usr/lib -type f | xargs md5sum.rb > md5.txt
システムには、いろいろな方法で、チェックサムを検証するツールがあったりすると思います。 セキュリティが心配される昨今ですので、自分なりに確認する方法を持つといいかもしれませんね。

■ 履歴

1.4 2007/5/7

MD5 の扱いでエラーになったので対応。

1.3 2002/5/5

usage の修正。

1.2 2002/5/5

最初の公開です。


渡辺哲也(WATANABE Tetsuya): Tetsuya.WATANABE atmark nifty.com