ファイルのコピーが正しく完了したかは、ファイルサイズの比較だけでは確認できない。サイズが同じでも、中身が壊れていることがある。shasum -a 256でSHA-256のハッシュ値を計算して、コピー元とコピー先で比較すると、中身が一致しているかを確認できる。
$ shasum -a 256 a.txt
a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447 a.txt
先頭の64文字がハッシュ値で、後ろがファイル名である。コピー元とコピー先のハッシュ値が同じであれば、中身は一致している。実行例はmacOS 27.0.1のもの。
サイズが同じでも中身が違う場合を検出する
a.txtとc.txtは、サイズが同じで、1文字だけが異なるファイルである。
$ wc -c a.txt c.txt
12 a.txt
12 c.txt
24 total
$ shasum -a 256 a.txt c.txt
a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447 a.txt
30e731839774de9ea08ff1adb8aa6b638e05f64900d005f84aea563cab0092b5 c.txt
ハッシュ値は、1文字の違いでまったく異なる値になる。サイズの比較では検出できない破損を、ハッシュ値の比較で検出できる。
-a 256でアルゴリズムを指定する
shasumは、オプションなしではSHA-1のハッシュ値を計算する。SHA-256を使うには、-a 256を付ける。
$ shasum a.txt
22596363b3de40b06f981fb85d82312e8c0ed511 a.txt
$ shasum -a 256 a.txt
a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447 a.txt
-aには、1、224、256、384、512などを指定できる。
LinuxやGNU coreutilsのsha256sumも、同じ形式で出力する。macOSでは、openssl dgst -sha256 a.txtでも、同じハッシュ値が得られる。
2つのファイルのハッシュ値を比較する
ハッシュ値だけを取り出すには、cutで最初の列を抜き出す。2つのファイルのハッシュ値を変数に入れて比較する。
l=$(shasum -a 256 a.txt | cut -d' ' -f1)
r=$(shasum -a 256 /path/to/copy/a.txt | cut -d' ' -f1)
[[ -n $l && $l == $r ]] && echo MATCH || echo MISMATCH
-n $lは、ハッシュ値が空でない条件である。両方のファイルを読み込めず、空のハッシュ値どうしをMATCHと誤判定する事態を防ぐ。存在しないファイルを指定すると、shasumはエラーメッセージを出力して、終了コード1で終了する。
$ shasum -a 256 nope.txt
shasum: nope.txt: No such file or directory
$ echo $?
1
ディレクトリ全体を検証する
コピー元のハッシュ値の一覧をファイルに保存して、コピー先でshasum -cを実行すると、ディレクトリ全体を検証できる。
$ (cd src && find . -type f -print0 | sort -z | xargs -0 shasum -a 256) > src.sha
$ (cd dst && shasum -a 256 -c --quiet ../src.sha)
-print0とxargs -0は、ファイル名に空白を含む場合に備えている。-cは、一覧のハッシュ値と、実際のファイルのハッシュ値を比較する。--quietを付けると、一致したファイルのOKは表示されない。
差分がある場合は、不一致のファイルだけが表示されて、終了コードが1になる。
$ (cd dst && shasum -a 256 -c --quiet ../src.sha)
./a.txt: FAILED
shasum: ./dir one/sub/c.txt: No such file or directory
./dir one/sub/c.txt: FAILED open or read
shasum: WARNING: 1 listed file could not be read
shasum: WARNING: 1 computed checksum did NOT match
$ echo $?
1
| 表示 | 意味 |
|---|---|
FAILED | ハッシュ値が一致しない。ファイルの中身が異なる |
FAILED open or read | コピー先にファイルがない、あるいは読み込めない |
shasum -cは、一覧にあるファイルだけを検証する。コピー先だけにあるファイルは検出されない。コピー先の余分なファイルは、rsyncのドライランで確認する。
shasumで作成した一覧は、GNU coreutilsのsha256sum -cでも検証できる。
参考: 【rsync】コピー結果をドライランで検証する (-n -c)
コピーの完了を待ってから検証する
ハッシュ値の計算は、ファイルを最初から最後まで読み込むため、時間がかかる。コピーとハッシュ値の計算を並行して実行したいときは、コピーのログに完了の印が書かれるまで待機して、検証を始めるスクリプトを使う。
#!/bin/bash
# usage: verify-copy.sh <copy-log> <verify-log> <src-file> <dst-file>
# waits for "=== DONE" in the copy log, then compares SHA-256 of both files
copy_log=$1
verify_log=$2
src_file=$3
dst_file=$4
until grep -q '=== DONE' "$copy_log"; do sleep 20; done
echo "$(date '+%T') hashing source" >> "$verify_log"
l=$(shasum -a 256 "$src_file" | cut -d' ' -f1)
echo "$(date '+%T') hashing destination" >> "$verify_log"
r=$(shasum -a 256 "$dst_file" | cut -d' ' -f1)
echo "src=$l" >> "$verify_log"
echo "dst=$r" >> "$verify_log"
[[ -n $l && $l == $r ]] && echo "RESULT: MATCH" >> "$verify_log" || echo "RESULT: MISMATCH" >> "$verify_log"
nohupでバックグラウンドに実行しておけば、コピーが終わると自動で検証が始まる。
$ nohup ./verify-copy.sh copy.log verify.log big.tar /path/to/copy/big.tar >/dev/null 2>&1 &
コピーのログに=== DONEが書かれると、ハッシュ値の計算が始まる。結果はverify.logの最後の行で確認できる。
$ cat verify.log
07:55:10 hashing source
07:55:12 hashing destination
src=a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447
dst=a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447
RESULT: MATCH
ハッシュ値が一致しなかったときは、RESULT: MISMATCHになる。ファイル全体を転送し直す前に、どの範囲が異なるかを調べると、書き直す範囲を絞り込める。
参考: 【cmp,dd】コピー先ファイルの壊れた範囲だけを書き直す
