一度にバイト数とsha1sumを取得する方法は?

一度にバイト数とsha1sumを取得する方法は?

コマンド出力のバイト数と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がプロセスリダイレクトが完了するのを待つようにラップします。cmdteezshsha1sumwccmd{...}

ここで、両方の出力は数バイト以下で保証されて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 pipefailpipechecksumチェックサムが一致しないときにゼロ以外の値を返す代わりに:

function pipechecksum
{
  { tee /dev/fd/$N | sha1sum --quiet -c <(awk '$2="-"' "${1?}") >&2; } {N}>&1
}

よさそうですが、今日も持ってきたのですが、まだ確定しているとは思えません。

関連情報