UNIX の小技
2009/7/2 木曜日10:17:131. pv: パイプラインの速度表示
pv はパイプライン中を流れるデータの速度や予想終了時間を表示します。
pv - Pipe Viewer
http://www.ivarch.com/programs/pv.shtml
Fedora なら yum install pv でインストールできます。RHEL/CentOS なら上記から RPM を入手してインストールします。
(例 1) 圧縮速度を測定し、終了時刻を予想する
pv /dev/sda | gzip > sda.gz
(例 2) 圧縮比を予測する
pv -cN read /dev/sda | gzip | pv -cN write > /dev/null
-N : 名前をつける
-c : エスケープシーケンスを使って描画する
参照: http://www.catonmat.net/blog/unix-utilities-pipe-viewer/
(例 3) データフローの速度を規制する
pv -L 1m /dev/sda | gzip > /dev/null
2. bash のプロセス置換
bash にはプロセス置換という構文があり、プロセスの入出力をファイルのように取り扱うことができます(zsh 由来の機能)。
(1)>(list)
プロセスへの書き込みをファイルとして取り扱うことができます。tee と組み合わせて、パイプラインを分岐させるのが典型的な用途です。
(例 1) md5sum を計算しながら gzip で圧縮する
cat file | tee >(md5sum > file.MD5SUM) | gzip > file.gz
(例 2) 同上、md5sum は標準エラーに表示
tee >(md5sum 1>&2) < file | gzip > file.gz
md5sum と gzip を別々に実行するのに比べて無駄がないため、入力が低速な場合、入力が巨大な場合、入力が一度しか読めない場合などに有用です。
(2)>(list)
プロセスの出力をファイルとして読み取ることができます。
(例 1) 一時ファイルを作らずに複数のパイプラインを組み合わせる
paste <(cut -d : -f 1 /etc/passwd) <(cut -d : -f 7 /etc/passwd)
参考ネタ: http://journal.mycom.co.jp/column/zsh/012/index.html
(例 2) 標準入力を受け付けないコマンドにデータを流し込む
diff <(echo foo) <(echo bar)
参考: http://d.hatena.ne.jp/oraccha/20080902/1220368128
(例 3) 通常のパイプ処理もプロセス置換で書き換えることができる
du -s /etc/* | sort -nr | head
↓
head <(sort -nr <(du -s *))
(3) 説明
プロセス置換は、名前付きパイプ(または名前付きオープンファイル)を使って実装されています。>(list) は内部的には以下のように処理されます。
1. FIFO を作る
2. FIFO の読み出し側を list の標準入力に接続する
3. list の実行を開始する
4. FIFO の名前(書き込み側)を置換して返す
自前で FIFO を作れば、bash/zsh 以外でも可能です。
例: md5sum を計算しながら gzip で圧縮する
mkfifo fifo
md5sum < fifo & tee fifo < file | gzip > file.gz
rm -f fifo
参考: http://www.codecomments.com/archive287-2006-1-778879.html
3. gzip
gzip は、ファイルを圧縮する場合、ファイル名やタイムスタンプ情報などの属性をヘッダに保持するため、同じ内容のファイルを圧縮しても異なる結果になる場合があります。しかし、-n (–no-name)オプションつきで実行すると、gzip は決定的(deterministic)です。つまり、同じ入力バイトストリームに対しては常に同じ出力バイトストリームを返します。gzip の出力を(md5sum などと同様な)暗号学的ハッシュとみなして利用することも可能です。例外として、Python の gzip モジュールは常に現在時刻をセットするため、非決定的です。http://bugs.python.org/issue4272マルチプロセッサ対応版の mgzip は、スレッド数(-t)によらず、つねに決定的です。-n オプションはなく、ファイル名・タイムスタンプ情報は記録されません。
4. dd
古典的プログラムでありながら今でも使われている dd ですが、パイプライン中に挿入する場合は bs= や count= が直感どおりに動作しないので注意が必要です。
これは、「短い複数の入力ブロックをまとめない」という dd の仕様によります。
dd の内部動作は以下の通り。まず、入力用/出力用の 2 つのバッファを用意します。ただし、bs が与えられ、ibs/obs が与えられず、かつ、コード変換をしない場合は、入出力共用のバッファを 1 つだけを使います。そして、入力をスキップし、出力をシークします。メインループでは、ibs バイトの読み込みを試みます。実際に読み込んだバイト数が ibs に達しない場合でも(特に 入力がパイプの場合、PIPE_BUF = 4KiB以下になります)、パーシャルブロックとして処理します。
http://www.opengroup.org/onlinepubs/009695399/utilities/dd.html
It shall read the input one block at a time, using the specified input block size; it shall then process the block of data actually returned, which could be smaller than the requested block size.
:
If the bs= exproperand is specified and no conversions other than sync, noerror, or notrunc are requested, the data returned from each input block shall be written as a separate output block; if the read returns less than a full block and the sync conversion is not specified, the resulting output block shall be the same size as the input block.
http://broadh2o.net/docs/unix/general/dd-cpio-etc.htm
Most people use dd incorrectly. This is because dd is a piece of junk that should be replaced. For example, one common misusage of dd is to try and get 64k blocks written to the tape with this command: tar -cf - args… | dd of=/dev/rmt8 bs=64k This won’t work because (as you will see below), the bs argument gives you only one buffer. The dd process will attempt to read 64k chunks from the pipe into this buffer, but will only receive a maximum of PIPE_BUF bytes (usually 4 or 8k). It will then write this buffer out to the tape as a single record (it will not pad this block to 64k, fortunately).
したがって、「先頭の 1GiB を取り出して」というつもりで
tar cf - /usr | dd bs=1M count=1024 of=foo
などとすると、出力は 1GiB よりも小さくなります。
この場合は dd ではなく head を使って
tar cf - /usr | head -c 1024m > foo
とするのがよいでしょう。
「2 GiB 目から 1GiB 分を取り出して」なら、
tar cf - /usr | tail -c +$((1024*1024*1024+1)) | head -c 1024m > foo
tail は通常「末尾から N 項目」ですが、+N 構文を使うと「先頭から N 番目以降」という意味になります。