func() {
echo 'hello'
echo 'This is an error' >&2
}
a=$(func)
b=???
一時ファイルを作成せずにstderrをb
変数にリダイレクトしたいのですが。
echo $b
# output should be: "This is an error"
機能するが一時ファイルを使用するソリューション:
touch temp.txt
exec 3< temp.txt
a=$(func 2> temp.txt);
cat <&3
rm temp.txt
だから問題は、一時ファイルを必要とせずに bash 関数stderr
のfunc
を変数b
にリダイレクトするにはどうすればよいですか?
Linuxおよびシェル(ここでは、bash
のように書き込み可能な一時ファイルを含む)を実装するシェルで、次のことができます。
{
out=$(
chmod u+w /dev/fd/3 && # needed for bash5+
ls /dev/null /x 2> /dev/fd/3
)
status=$?
err=$(cat<&3)
} 3<<EOF
EOF
printf '%s=<%s>\n' out "$out" err "$err" status "$status"
(ls /dev/null /x
は、stdoutとstderrの両方で何かを出力するコマンドの例です)。
zsh
を使用すると、次のこともできます。
(){ out=$(ls /dev/null /x 2> $1) status=$? err=$(<$1);} =(:)
(=(cmd)
は一時ファイルを使用するプロセス置換の形式であり、(){ code; } args
匿名関数です)。
いずれの場合も、一時ファイルを使用します。パイプを使用するソリューションでは、出力が大きい場合にデッドロックが発生しやすくなります。 2つの別々のパイプを介してstdoutとstderrを読み取り、select()
/poll()
といくつかのループ内の読み取りを使用して、ロックアップを引き起こさずに2つのパイプからデータを読み取ることができますが、かなり関与していて、AFAIK、zsh
だけがselect()
サポートを組み込み、yash
だけがpipe()
への未加工インターフェースを持っています(詳細は 同じファイル記述子への読み取り/書き込みシェルリダイレクト )。
別のアプローチは、一時ファイルではなく一時メモリにストリームの1つを格納することです。似ている(zsh
またはbash
構文):
{
IFS= read -rd '' err
IFS= read -rd '' out
IFS= read -rd '' status
} < <({ out=$(ls /dev/null /x); } 2>&1; printf '\0%s' "$out" "$?")
(コマンドがNULを出力しないと仮定)
$err
には、末尾の改行文字が含まれることに注意してください。
他のアプローチとしては、stdoutとstderrを別々に装飾し、読み取り時に装飾を削除することができます。
out= err= status=
while IFS= read -r line; do
case $line in
(out:*) out=$out${line#out:}$'\n';;
(err:*) err=$err${line#err:}$'\n';;
(status:*) status=${line#status:};;
esac
done < <(
{
{
ls /dev/null /x |
grep --label=out --line-buffered -H '^' >&3
echo >&3 "status:${PIPESTATUS[0]}" # $pipestatus[1] in zsh
} 2>&1 |
grep --label=err --line-buffered -H '^'
} 3>&1
)
これは、GNU grep
であり、行が十分に短いことを前提としています。PIPEBUF(Linuxでは4K)より大きい行では、2つのgrep
sの出力の行がチャンクにまとまって破損する可能性があります。
まあ、一時ファイルなしでstderrを1つの変数にキャプチャし、stdoutを別の変数にキャプチャするのは簡単ではありません。
これが機能する例です
func() {
echo 'hello'
echo 'This is an error' >&2
}
result=$(
{ stdout=$(func) ; } 2>&1
echo -e "mysuperuniqueseparator\n"
echo -e "${stdout}\n"
)
var_out=${result#*mysuperuniqueseparator$'\n'}
var_err=${result%$'\n'mysuperuniqueseparator*}
汚い方法です。stderrをstdoutにリダイレクトし、両方を1つの変数に区切り記号を付けて入れ、2つの部分に分割します。
プラス:
明らかに、これは堅牢ではありません。コマンドの標準出力または標準エラーには、使用する区切り文字列が含まれている可能性があるためです。