web-dev-qa-db-ja.com

stderrとは別にstdoutをバッファリングする

私は以下を出力するユーティリティを実行しています:

  • 標準エラーへの進展
  • データ/収量/標準出力への出力

ユーティリティをビルドしなかったし、簡単に変更することもできなかった。

次のことを行いたい:

  • 標準エラーを直接標準出力に送信する
  • そのOutputをバッファし、標準出力にフラッシュしますコマンドが終了したら
    (ここにある可能性のあるデータは10KiB未満なので、RAMは問題ありません。)

これはPOSIX sh(およびLinuxOpenBSDの両方に共通のユーティリティのみを呼び出す)で実行できますが、不確定性/潜在的な競合状態などは発生しません。名前付きパイプまたは一時ファイルから?

4

あなたは次のようなことができるはずです:

{
  cmd 2>&3 3>&- |
    awk '    {saved = saved $0 ORS}
         END {printf "%s", saved}' 3>&-
} 3>&1

ここでは、awkを使用してcmdのすべての出力を保持しています(cmdがstderr出力をスクリプトのstdoutに書き込んだ後)。

awkは、パイプの書き込みが終了するまで読み込まれます。通常、これはcmd(およびそれがforkし、パイプへのfdを保持しているすべてのプロセス)が終了したときにのみ発生します。何らかの理由でcmdがstdoutを明示的に閉じ、後でstderrにさらに進捗状況を書き込む場合、余分な進捗状況は最終的にafter通常の出力になる可能性があります。 cmd(cmd; exit)に置き換えることで回避できます。awkは、そのサブシェル(パイプにも標準出力が開いている)が終了するのを待ち、そのサブシェルcmdが終了するのを待ちます(そして、その終了ステータスをexitで報告します)。

しかし、それは行儀のよいcmdでは必要ありません。これは、cmdが子プロセスをforkしている(そして待機していない)場合、そのstdoutがリダイレクトされ、awkまたはそのスクリプトが終了した後もstderrに書き込む可能性がある場合には対処しません(おそらく、そのstdoutを明示的に閉じるコマンドよりも可能性が高いシナリオです)。

cmdの出力がテキストではない場合、すべてのawk実装がバイト0または余分な長い行を処理できるわけではないことに注意してください。改行文字がまだ含まれていない場合は、末尾に追加されます入力。

POSIXツールチェストには、任意の量のバイナリデータをメモリに格納して後で表示できるコマンドはありません。

Perlが利用可能な場合は、awkコマンドをPerl -0777 -pe ''だけに置き換えることができます。

ここでは、メモリの代わりに、出力を一時ファイルに保存できます。これは、バイナリ出力の問題に対処し、より大きな出力に拡張する可能性があります。

残念ながら、一時ファイルを確実に作成する唯一のPOSIXの方法はm4ユーティリティを使用することですが、そのユーティリティは(POSIXで義務付けられているものであっても)最近のプロダクションシステムで常に見つかるわけではありません。おそらく、m4よりもPerlを見つける可能性が高くなります。

いずれの場合も、それは次のようになります。

die() {
  [ "$#" -eq 0 ] || printf >&2 '%s\n' "$@"
  exit 1
}

tmpdir=${TMPDIR:-/tmp}
tmpfile=$(
  echo 'mkstemp(TEMPLATE)' |
    m4 -D "TEMPLATE=${tmpdir%/}/XXXXXXX"
) && [ -n "$tmpfile" ] || die 'Cannot get a temp file'

{
  rm -f -- "$tmpfile" || die "Cannot remove $tmpfile"
  cmd 2>&1 >&3 3>&- 4<&-
  cat <&4
} 3> "$tmpfile" 4< "$tmpfile"

ここでは、一時ファイルを開いた後、クリーンアップを処理するための適切な方法としてcmdを実行する前に、一時ファイルのリンクを解除します。

GNUのみを対象としている場合(「Linux」はOSではなく、さまざまなOSで見つかったカーネルであり、一部にはsh)およびOpenBSDシステムでは、m4の代わりにmktempを使用して一時ファイルを作成できるはずです。

4
#!/bin/bash

mycmd() {
  echo progress >&2
  echo out
  sleep 1
  echo progress >&2
  echo out
  sleep 1
  echo progress >&2
  echo out
  sleep 1
}

# Make sure the tempfile is made in a secure way that avoids indeterminism / potential race conditions / etc.
tmpfile="$(tempfile)"
# Write output to the file
mycmd 2>&1 >"$tmpfile"; cat "$tmpfile"; rm "$tmpfile"

# or if output is small: Write it to an environment variable.
# These are below the limit for "small" for different shells
# (Determined by making "mycmd" output that amount of data)
# ash  30 GB
# dash 30 GB
# bash 3 GB
# zsh  3 GB
# ksh  1 GB
out=`mycmd` 2>&1
echo "$out"

両方のソリューションは、ルート(ファイルシステム上および/ proc/*/environ内)に表示​​され、システム上の他のユーザーには表示されません。

システムにspongeがあり、シェルが/ dev/stdoutをサポートしている場合:

{
  cmd 2>&3 3>&- |
    sponge /dev/stdout 3>&-;
} 3>&1

(Kshバージョン:

$ ksh --version
  version         sh (AT&T Research) 93u+ 2012-08-01

2
Ole Tange