私は以下を出力するユーティリティを実行しています:
ユーティリティをビルドしなかったし、簡単に変更することもできなかった。
次のことを行いたい:
これはPOSIX sh(およびLinuxとOpenBSDの両方に共通のユーティリティのみを呼び出す)で実行できますが、不確定性/潜在的な競合状態などは発生しません。名前付きパイプまたは一時ファイルから?
あなたは次のようなことができるはずです:
{
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
を使用して一時ファイルを作成できるはずです。
#!/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
)