一度にバイト数と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:出力を複数回使用し、結果を連結します。

おすすめ記事