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

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

以下をエクスポートするユーティリティを実行しています。

  • 標準誤差の進行
  • データ/出力/出力を標準出力に

私はユーティリティを構築しておらず、簡単に修正することはできません。

私は次のことをしたいと思います:

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

POSIX shでこれを行うことはできますか(そしてLinux共通ユーティリティを呼び出すだけです)そしてOpenBSD)、名前付きパイプや一時ファイルによって引き起こされる不確実性/潜在的な競合条件などはありませんか?

答え1

次のことができる必要があります。

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

awkこれはすべての出力を保存するために使用されますcmdcmdstderr出力をスクリプトのstdoutに書き込んだ後)。

awkパイプの書き込み端が閉じるまで読みます。通常、cmdこれはパイプのfdをまだ保持しているすべての分岐プロセスが完了した場合にのみ発生します。何らかの理由でcmdstdoutを明示的に閉じてstderrに追加の進捗状況を記録することを決定した場合は、追加の進捗状況が表示されることがあります。後ろに通常出力。このサブシェル(パイプの標準出力も開いています)が完了するのを待つ場所と、そのサブシェルが完了するのを正確に待機する場所(および終了ステータスを報告する場所)を置き換えることで、cmdこの問題を解決できます。あります。(cmd; exit)awkcmdexit

しかし、行動の良い人にはこれは必要ありませんcmd。これはまた、スクリプトが完了してから長い間、またはスクリプトが完了した後でさえ(おそらく次の古いコマンドを使用して)stderrに書き込むことができるサブcmdプロセスとそのstdoutリダイレクトをフォーク(待たずに)する場合も解決しません。awk明示的な終了)可能性が高い)標準出力)。

cmd出力がテキストでない場合、すべてのawk実装がバイト0または非常に長い行を処理できるわけではなく、入力にまだない場合は改行が最後に追加されます。

POSIX ツールボックスには、メモリにランダムな量のバイナリデータを保存し、後で表示するコマンドはありません。

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

ここでは、出力をメモリの代わりに一時ファイルに保存できます。これにより、バイナリ出力の問題が解決され、より大きな出力にさらに拡張できます。

残念ながら、一時ファイルを確実に生成する唯一のPOSIX方法は、このm4ユーティリティを使用することです。このユーティリティは、今日の運用システム(POSIX必須システムでも)では常に見つかりません。perlより見つける可能性が高いかもしれませんm4

とにかく、これは次のようになります。

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」はオペレーティングシステムではなく、さまざまなオペレーティングシステムで見つかるカーネルだけであり、一部は1つもない場合もあります)とOpenBSDシステムのみを対象とする場合は、一時ファイルを生成できるはずです。shを使用して 。mktempm4

答え2

#!/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

)

関連情報