コマンド出力のバイト数とsha1sumを取得したいです。
原則として、常に次のようにすることができます。
BYTES="$( somecommand | wc -c )"
DIGEST="$( somecommand | sha1sum | sed 's/ .*//' )"
...しかし、私が興味のあるユースケースでは、somecommand
時間がかかり、多くの出力を生成するので、一度だけ呼び出すことをお勧めします。
私が考えた方法の1つはこれです。
evil() {
{
somecommand | \
tee >( wc -c | sed 's/^/BYTES=/' ) | \
sha1sum | \
sed 's/ .*//; s/^/DIGEST=/'
} 2>&1
}
eval "$( evil )"
…効果があるようでしたが、中では死にそうです。
パイプラインの複数のセグメントの出力を別々の変数としてキャプチャするより良い(より強力でより一般的な)方法があるかどうか疑問に思います。
編集:現在解決している問題は、bash
このシェルソリューションに最も興味があるため、プログラミングもたくさん行われているため、zsh
そのソリューションにも興味があります。
EDIT2:Stéphane Chazelasのソリューションをに移植しようとしましたが、うまくいきませbash
んでした。
#!/bin/bash
cmd() {
printf -- '%1000s'
}
bytes_and_checksum() {
local IFS
cmd | tee >(sha1sum > $1) | wc -c | read bytes || return
read checksum rest_ignored < $1 || return
}
set -o pipefail
unset bytes checksum
bytes_and_checksum "$(mktemp)"
printf -- 'bytes=%s\n' $bytes
printf -- 'checksum=%s\n' $checksum
上記のスクリプトを実行すると、私が得た結果は次のようになります。
bytes=
checksum=96d89030c1473585f16ec7a52050b410e44dd332
値がchecksum
正しいです。なぜbytes
値が設定されていないのかわかりません。
EDIT3:いいですね。 @muruのヒントのおかげで問題を解決しました。
#!/bin/bash
cmd() {
printf -- '%1000s'
}
bytes_and_checksum() {
local IFS
read bytes < <( cmd | tee >(sha1sum > $1) | wc -c ) || return
read checksum rest_ignored < $1 || return
}
set -o pipefail
unset bytes checksum
bytes_and_checksum "$(mktemp)"
printf -- 'bytes=%s\n' $bytes
printf -- 'checksum=%s\n' $checksum
今:
bytes=1000
checksum=96d89030c1473585f16ec7a52050b410e44dd332
残念ながら...
bytes_and_checksum
...上記の例よりもはるかに多くの出力を生成すると、私の関数は中断されます(デッドロック?)。cmd
再描画ボードに戻って...
答え1
一時ファイルを使用する方が簡単です。存在するzsh
:
(){set -o localoptions -o pipefail; local IFS
{cmd} > >(sha1sum > $1) | wc -c | read bytes || return
read checksum rest_ignored < $1 || return
} =()
多くのwc
実装には、出力する数字の周りにスペースが含まれています。read
デフォルトは$IFS
削除することです。
の終了状態がsha1sum
失われます。
一時ファイルが作成される=()
と、内容はまったく出力されません。一時ファイルは、指定されたコマンド(ここでは匿名関数)が返されると自動的に削除されます。
の出力は2回リダイレクトされるため、dによって内部的に処理されるため、cmd > file | other-cmd
ここにはtoとtoがあります。 zshがプロセスリダイレクトが完了するのを待つようにラップします。cmd
tee
zsh
sha1sum
wc
cmd
{...}
ここで、両方の出力は数バイト以下で保証されてsha1sum
おりwc
、パイプに転送される可能性があり、同時にそのパイプから読み取る必要はありません(zshには次のためのselect()
インターフェースがあるためこれを行うことができます)。 / poll()
、しかしbashではない)。これはデッドロックを引き起こさずに順次実行できるため、ここに簡単なバージョンがあります。さまざまな変数で。
Linuxベースのシステム(/dev/fd/x
パイプx
のfdが名前付きパイプのように動作する場合):
{
IFS=$' \t' read bytes < <(cmd 3<&- | tee >(sha1sum > /dev/fd/3) | wc -c)
IFS=$' \t' read sum rest <&3
} 3< <(:)
(Bashでも動作します)。
より大きな出力で発生するデッドロックの詳細については、以下を参照してください。tee + cat:出力を複数回使用し、結果を連結します。。
答え2
バックアップを使用しています強く打つ「仮定されたファイル名」を引数として使用する次のヘルパー「中間」関数を持つスクリプト(下記のtar.gzの例を参照):
function pipesum
{
tee >(sha1sum | awk --assign F="${1##*/}" '$2=F' > "${1?}.sha1")
}
function pipelen
{
tee >(wc -c > "${1?}.len")
}
function pipesumlen
{
tee >(sha1sum | awk --assign F="${1##*/}" '$2=F' > "${1?}.sha1") >(wc -c > "${1?}.len")
}
function pipechecksum
{
tee >(sha1sum --quiet -c <(awk '$2="-"' "${1?}") >&2)
}
例:
$ echo 123 | pipesumlen filename
123
$ ls filename*
filename.len filename.sha1
$ cat filename*
4
a8fdc205a9f19cc1c7507a60c4f01b13d11d7fd0 filename
$ echo 123 | pipechecksum filename.sha1
123
$ echo 1234 | pipechecksum filename.sha1
1234
-: FAILED
sha1sum: WARNING: 1 computed checksum did NOT match
私はこれを次のように時間がかかり、CPUとIOを大量に消費するスクリプトで使用しています。
tar |
pipesumlen mybackup.tar |
gzip > mybackup.tar.gz
<mybackup.tar.gz gunzip |
pipechecksum mybackup.tar.sha1 |
xz > mybackup.tar.xz
そのため、ランダムメモリ/ディスクビットフリップに基づいてバックアップを確認しました。 「mybackup.tar」が実際に作成され、チェックサムされるように、「mybackup.tar.sha1」ファイルが生成されます。実際、この例では、圧縮されていないデータはディスクに書き込まれません。
警告:pipechecksum
エラーが発生してもスクリプトは終了しませんset -euo pipefail
。pipechecksum
チェックサムが一致しないときにゼロ以外の値を返す代わりに:
function pipechecksum
{
{ tee /dev/fd/$N | sha1sum --quiet -c <(awk '$2="-"' "${1?}") >&2; } {N}>&1
}
よさそうですが、今日も持ってきたのですが、まだ確定しているとは思えません。