一時ファイルを使用せずに、stdoutとstderrを異なる変数に保存またはキャプチャできますか?現時点では、some_command
の実行時にout
でstdoutを、err
でstderrを取得するためにこれを行いますが、一時ファイルは避けたいと思います。
error_file=$(mktemp)
out=$(some_command 2>$error_file)
err=$(< error_file)
rm $error_file
OK、少し、いですが、ここに解決策があります:
unset t_std t_err
eval "$( (echo std; echo err >&2) \
2> >(readarray -t t_err; typeset -p t_err) \
> >(readarray -t t_std; typeset -p t_std) )"
(echo std; echo err >&2)
は実際のコマンドに置き換える必要があります。 stdoutの出力は、改行($t_std
)とstderrを-t
に省略する行ごとに、配列$t_err
に保存されます。
配列が気に入らない場合はできます
unset t_std t_err
eval "$( (echo std; echo err >&2 ) \
2> >(t_err=$(cat); typeset -p t_err) \
> >(t_std=$(cat); typeset -p t_std) )"
これは、var=$(cmd)
の動作をほとんど模倣しますが、$?
の値は例外で、最後の変更が行われます。
unset t_std t_err t_ret
eval "$( (echo std; echo err >&2; exit 2 ) \
2> >(t_err=$(cat); typeset -p t_err) \
> >(t_std=$(cat); typeset -p t_std); t_ret=$?; typeset -p t_ret )"
ここで$?
は$t_ret
に保存されます
[〜#〜] gnu [〜#〜]bash
、バージョン4.2.37(1)-を使用してDebian wheezyでテスト済みリリース(i486-pc-linux-gnu)。
ジョナサンには 答え があります。参考までに、これはksh93のトリックです。 (非古代バージョンが必要です)。
function out {
echo stdout
echo stderr >&2
}
x=${ { y=$(out); } 2>&1; }
typeset -p x y # Show the values
生産する
x=stderr
y=stdout
${ cmds;}
構文は、サブシェルを作成しない単なるコマンド置換です。コマンドは現在のシェル環境で実行されます。最初のスペースは重要です({
は予約語です)。
内部コマンドグループのStderrはstdoutにリダイレクトされます(内部置換に適用されるように)。次に、out
のstdoutがy
に割り当てられ、リダイレクトされたstderrがx
によってキャプチャされます。コマンド置換のy
は通常失われません。サブシェル。
他のシェルでは不可能です。出力をキャプチャするすべてのコンストラクトでは、プロデューサーをサブシェルに配置する必要があり、この場合は割り当てが含まれます。
update:mkshでもサポートされるようになりました。
このコマンドは、現在実行中のシェルにstdout(stdval)とstderr(errval)の両方の値を設定します。
eval "$( execcommand 2> >(setval errval) > >(setval stdval); )"
この関数が定義されている場合:
function setval { printf -v "$1" "%s" "$(cat)"; declare -p "$1"; }
Execcommandを「ls」、「cp」、「df」などのキャプチャされたコマンドに変更します。
これはすべて、関数setvalを使用してすべてのキャプチャされた値をテキスト行に変換できるという考えに基づいており、setvalはこの構造の各値をキャプチャするために使用されます。
execcommand 2> CaptureErr > CaptureOut
各キャプチャ値をsetval呼び出しに変換します。
execcommand 2> >(setval errval) > >(setval stdval)
すべてを実行呼び出し内にラップしてエコーします。
echo "$( execcommand 2> >(setval errval) > >(setval stdval) )"
各setvalが作成する宣言呼び出しを取得します。
declare -- stdval="I'm std"
declare -- errval="I'm err"
そのコードを実行(および変数セットを取得)するには、evalを使用します。
eval "$( execcommand 2> >(setval errval) > >(setval stdval) )"
そして最後にセット変数をエコーします:
echo "std out is : |$stdval| std err is : |$errval|
戻り値(終了)を含めることもできます。
完全なbashスクリプトの例は次のようになります。
#!/bin/bash --
# The only function to declare:
function setval { printf -v "$1" "%s" "$(cat)"; declare -p "$1"; }
# a dummy function with some example values:
function dummy { echo "I'm std"; echo "I'm err" >&2; return 34; }
# Running a command to capture all values
# change execcommand to dummy or any other command to test.
eval "$( dummy 2> >(setval errval) > >(setval stdval); <<<"$?" setval retval; )"
echo "std out is : |$stdval| std err is : |$errval| return val is : |$retval|"
これは、異なる変数でstdoutとstderrをキャッチするためのものです。
stderr
をキャッチするだけで、stdout
をそのままにしたい場合は、より適切で短いソリューションがあります 。
bash
ソリューションこのバージョンはサブシェルを使用し、tempfile
sなしで実行されます。 (サブシェルなしで実行されるtempfile
バージョンについては、 my other answer を参照してください。)
: catch STDOUT STDERR cmd args..
catch()
{
eval "$({
__2="$(
{ __1="$("${@:3}")"; } 2>&1;
ret=$?;
printf '%q=%q\n' "$1" "$__1" >&2;
exit $ret
)"
ret="$?";
printf '%s=%q\n' "$2" "$__2" >&2;
printf '( exit %q )' "$ret" >&2;
} 2>&1 )";
}
使用例:
dummy()
{
echo "$3" >&2
echo "$2" >&1
return "$1"
}
catch stdout stderr dummy 3 $'\ndiffcult\n data \n\n\n' $'\nother\n difficult \n data \n\n'
printf 'ret=%q\n' "$?"
printf 'stdout=%q\n' "$stdout"
printf 'stderr=%q\n' "$stderr"
これは印刷します
ret=3
stdout=$'\ndiffcult\n data '
stderr=$'\nother\n difficult \n data '
したがって、それについて深く考えることなく使用できます。 catch VAR1 VAR2
をcommand args..
の前に置くだけで完了です。
一部のif cmd args..; then
はif catch VAR1 VAR2 cmd args..; then
になります。本当に複雑なことは何もありません。
Q:どのように機能しますか?
ここで他の回答からのアイデアを関数にラップするだけなので、簡単に再利用できます。
catch()
は、基本的にeval
を使用して2つの変数を設定します。これは https://stackoverflow.com/a/18086548 に似ています
catch out err dummy 1 2a 3b
の呼び出しを検討してください。
今のところeval "$({
と__2="$(
をスキップしましょう。これについては後で説明します。
__1="$("$("${@:3}")"; } 2>&1;
はdummy 1 2 3
を実行し、後で使用するためにstdout
を__1
に保存します。したがって、__1
は2a
になります。また、stderr
of dummy
をstdout
にリダイレクトし、外側のキャッチがstdout
を収集できるようにします。
ret=$?;
は、終了コードをキャッチします。これは1
です。
printf '%q=%q\n' "$1" "$__1" >&2;
は、out=2a
をstderr
に出力します。 stderr
は、現在のstdout
がstderr
コマンドのdummy
の役割をすでに引き継いでいるので、ここで使用されます。
exit $ret
は、終了コード(1
)を次のステージに転送します。
次に、外側の__2="$( ... )"
:
これは、上記のstdout
をキャッチして、stderr
呼び出しのdummy
を変数__2
に取り込みます。 (ここで__1
を再利用できますが、混乱を少なくするために__2
を使用しました。) __2
は3b
になります
ret="$?";
は(返された)戻りコード1
(dummy
から)を再びキャッチします
printf '%s=%q\n' "$2" "$__2" >&2;
は、err=3a
をstderr
に出力します。 stderr
は、他の変数out=2a
の出力にすでに使用されているため、再び使用されます。
printf '( exit %q )' "$ret" >&2; then outputs the code to set the proper return value. I did not find a better way, as assignig it to a variable needs a variable name, which then cannot be used as first oder second argument to
catch`。
最適化として、これら2つのprintf
をprintf '%s=%q\n( exit %q )
"$ __ 2" "$ ret" `のような単一のものとして書くこともできます。
では、これまでのところ何がありますか?
次はstderrに書かれています。
out=2a
err=3b
( exit 1 )
ここで、out
は$1
から、2a
はstdout
of dummy
から、err
は$2
から、3b
はstderr
of dummy
から、そして1
は、dummy
からの戻りコードからのものです。
printf
の形式の%q
は、eval
になるとシェルが適切な(単一の)引数を認識するように、引用を処理します。 2a
と3b
は非常に単純なので、文字通りコピーされます。
次に、外側のeval "$({ ... } 2>&1 )";
:
これは、2つの変数とexit
を出力する上記のすべてを実行し、それを(2>&1
のために)キャッチし、eval
を使用して現在のシェルに解析します。
このようにして、2つの変数が設定され、リターンコードも取得されます。
Q:eval
を使用していますが、これは悪です。それで安全ですか?
printf %q
にバグがない限り、安全です。しかし、常に非常に注意する必要があります。Shellshockについて考えてください。Q:バグ?
以下を除いて、明らかなバグは確認されていません。
通常どおり、$(echo $'\n\n\n\n')
は、最後の改行だけでなく、すべての改行を飲み込みます。これはPOSIXの要件です。 LFを無害にする必要がある場合は、次のレシピのように、出力に末尾の文字を追加して削除します(末尾のx
を見ると、$'\n'
で終わるファイルを指すソフトリンクを読み取ることができます):
target="$(readlink -e "$file")x"
target="${target%x}"
シェル変数はバイトNUL($'\0'
)を運ぶことができません。 stdout
またはstderr
で発生した場合、それらは単に無視されます。
指定されたコマンドはサブサブシェルで実行されます。したがって、$PPID
にアクセスすることも、シェル変数を変更することもできません。ビルトインでさえcatch
シェル関数を使用できますが、シェル変数を変更することはできません($( .. )
内で実行されているすべてがこれを実行できないため)。したがって、現在のシェルで関数を実行し、stderr/stdoutをキャッチする必要がある場合は、tempfile
sを使用して通常の方法でこれを行う必要があります。 (これを行う方法はあります。通常、シェルを中断しても残骸が残ることはありませんが、これは複雑であり、独自の答えに値します。)
Q:Bashバージョン?
printf %q
による)Q:これはまだとても厄介に見えます。
ksh
でどのようにすればよりきれいにできるかを示しています。しかし、私はksh
に慣れていないので、ksh
の同様の再利用しやすいレシピを作成するために他の人に任せています。Q:なぜksh
を使用しないのですか?
bash
ソリューションであるためQ:スクリプトを改善できます
Q:タイプミスがあります。 : catch STDOUT STDERR cmd args..
は# catch STDOUT STDERR cmd args..
を読み取るものとします
:
はbash -x
に表示され、コメントは黙って飲み込まれます。そのため、関数定義にタイプミスがある場合、パーサーの場所を確認できます。これは古いデバッグのトリックです。ただし、少し注意してください。:
の引数内に、きちんとしたsideffectsを簡単に作成できます。編集:catch()
からシングルライナーをより簡単に作成できるように、いくつかの;
を追加しました。そして、それがどのように機能するかのセクションを追加しました。
技術的には、名前付きパイプは一時ファイルではなく、ここで言及している人はいません。ファイルシステムには何も保存されず、接続するとすぐに削除できます(したがって、それらは表示されません)。
#!/bin/bash -e
foo () {
echo stdout1
echo stderr1 >&2
sleep 1
echo stdout2
echo stderr2 >&2
}
rm -f stdout stderr
mkfifo stdout stderr
foo >stdout 2>stderr & # blocks until reader is connected
exec {fdout}<stdout {fderr}<stderr # unblocks `foo &`
rm stdout stderr # filesystem objects are no longer needed
stdout=$(cat <&$fdout)
stderr=$(cat <&$fderr)
echo $stdout
echo $stderr
exec {fdout}<&- {fderr}<&- # free file descriptors, optional
この方法で複数のバックグラウンドプロセスを設定し、都合のよい時間に標準出力と標準エラーを非同期に収集できます。
これが1つのプロセスでのみ必要な場合は、{fdout}/{fderr}
構文(無料のfdを見つける)の代わりに、3や4などのハードコードされたfd番号を使用することもできます。
Evalが気に入らなかったため、ここでは、リダイレクトトリックを使用してプログラム出力を変数にキャプチャし、その変数を解析してさまざまなコンポーネントを抽出するソリューションを示します。 -wフラグはチャンクサイズを設定し、中間形式のstd-out/errメッセージの順序に影響します。 1は、オーバーヘッドを犠牲にして潜在的に高解像度を提供します。
#######
# runs "$@" and outputs both stdout and stderr on stdin, both in a prefixed format allowing both std in and out to be separately stored in variables later.
# limitations: Bash does not allow null to be returned from subshells, limiting the usefullness of applying this function to commands with null in the output.
# example:
# var=$(keepBoth ls . notHere)
# echo ls had the exit code "$(extractOne r "$var")"
# echo ls had the stdErr of "$(extractOne e "$var")"
# echo ls had the stdOut of "$(extractOne o "$var")"
keepBoth() {
(
prefix(){
( set -o pipefail
base64 -w 1 - | (
while read c
do echo -E "$1" "$c"
done
)
)
}
( (
"$@" | prefix o >&3
echo ${PIPESTATUS[0]} | prefix r >&3
) 2>&1 | prefix e >&1
) 3>&1
)
}
extractOne() { # extract
echo "$2" | grep "^$1" | cut --delimiter=' ' --fields=2 | base64 --decode -
}
簡潔に言うと、答えは「いいえ」だと思います。キャプチャ$( ... )
は、変数への標準出力のみをキャプチャします。標準エラーを別の変数にキャプチャする方法はありません。だから、あなたが持っているのはそれが得るほどきれいです。
どうでしょう... = D
GET_STDERR=""
GET_STDOUT=""
get_stderr_stdout() {
GET_STDERR=""
GET_STDOUT=""
unset t_std t_err
eval "$( (eval $1) 2> >(t_err=$(cat); typeset -p t_err) > >(t_std=$(cat); typeset -p t_std) )"
GET_STDERR=$t_err
GET_STDOUT=$t_std
}
get_stderr_stdout "command"
echo "$GET_STDERR"
echo "$GET_STDOUT"
読者の利益のために、tempfile
sを使用したソリューションがあります。
問題はtempfile
sを使用しないことでした。ただし、これは、シェルが停止した場合のtempfileによる_/tmp/
_の不要な汚染による可能性があります。 _kill -9
_の場合、いくつかの_trap 'rm "$tmpfile1" "$tmpfile2"' 0
_は起動しません。
tempfile
を使用できるが、残骸を残さないできる状況にある場合behind、これがレシピです。
再び、それはcatch()
と呼ばれ(私の その他の答え として)、同じ呼び出し構文を持ちます:
_catch stdout stderr command args..
_
_# Wrappers to avoid polluting the current Shell's environment with variables
: catch_read returncode FD variable
catch_read()
{
eval "$3=\"\`cat <&$2\`\"";
# You can use read instead to skip some fork()s.
# However read stops at the first NUL byte,
# also does no \n removal and needs bash 3 or above:
#IFS='' read -ru$2 -d '' "$3";
return $1;
}
: catch_1 tempfile variable comand args..
catch_1()
{
{
rm -f "$1";
"${@:3}" 66<&-;
catch_read $? 66 "$2";
} 2>&1 >"$1" 66<"$1";
}
: catch stdout stderr command args..
catch()
{
catch_1 "`tempfile`" "${2:-stderr}" catch_1 "`tempfile`" "${1:-stdout}" "${@:3}";
}
_
それが何をする:
tempfile
とstdout
に対して2つのstderr
を作成します。ただし、これらはすぐに削除されるため、非常に短時間しか存在しません。
catch_1()
は、stdout
(FD 1)を変数にキャッチし、stderr
をstdout
に移動して、次(「左」)_catch_1
_はそれをキャッチできます。
catch
の処理は右から左に行われるため、左の_catch_1
_が最後に実行され、stderr
をキャッチします。
起こり得る最悪の事態は、いくつかの一時ファイルが_/tmp/
_に表示されることですが、その場合は常に空です。 (それらは満たされる前に削除されます。)。通常、LinuxではtmpfsがメインメモリのGBあたり約128Kのファイルをサポートするため、これは問題になりません。
指定されたコマンドは、すべてのローカルシェル変数にアクセスして変更することもできます。したがって、sideffectsがあるShell関数を呼び出すことができます!
これは、tempfile
呼び出しに対して2回だけ分岐します。
バグ:
tempfile
が失敗した場合の適切なエラー処理がありません。
これは、通常の_\n
_シェルの削除を行います。 catch_read()
のコメントを参照してください。
ファイル記述子_66
_を使用してデータをコマンドにパイプすることはできません。必要な場合は、_42
_などの別の記述子をリダイレクトに使用します(非常に古いシェルは最大9個のFDしか提供しないことに注意してください)。
これは、stdout
およびstderr
のNULバイト(_$'\0'
_)を処理できません。 (NULは無視されます。read
バリアントの場合、NULの背後にあるものはすべて無視されます。)
ご参考までに:
回避策の1つは、このページのいくつかの提案よりも直感的ですが、出力ストリームにタグを付け、それらをマージし、その後タグに基づいて分割することです。たとえば、stdoutに「STDOUT」プレフィックスをタグ付けする場合があります。
function someCmd {
echo "I am stdout"
echo "I am stderr" 1>&2
}
ALL=$({ someCmd | sed -e 's/^/STDOUT/g'; } 2>&1)
OUT=$(echo "$ALL" | grep "^STDOUT" | sed -e 's/^STDOUT//g')
ERR=$(echo "$ALL" | grep -v "^STDOUT")
`` `
Stdoutおよび/またはstderrが制限された形式であることがわかっている場合、許可されたコンテンツと競合しないタグを作成できます。
以下は、OPが望んでいたものではないが、他のオプションとは異なる、より単純なバリエーションです。ファイル記述子を再配置することで、必要なものを取得できます。
テストコマンド:
%> cat xx.sh
#!/bin/bash
echo stdout
>&2 echo stderr
それ自体は:
%> ./xx.sh
stdout
stderr
次に、stdoutを出力し、stderrを変数にキャプチャし、stdoutをファイルに記録します
%> export err=$(./xx.sh 3>&1 1>&2 2>&3 >"out")
stdout
%> cat out
stdout
%> echo
$err
stderr
または、stdoutをログに記録し、stderrを変数にキャプチャします。
export err=$(./xx.sh 3>&1 1>out 2>&3 )
%> cat out
stdout
%> echo $err
stderr
あなたはアイデアを得る。
警告:NOT(まだ?)WORKING!
以下は、一時ファイルを作成せずにPOSIX shのみで動作させるための潜在的なリードと思われます。ただし、base64が必要であり、エンコード/デコードのためにそれほど効率的でなく、「より大きな」メモリも使用する場合があります。
ただし、主な問題は、すべてが際どいように見えることです。次のようなexeを使用してみてください。
exe(){cat /usr/share/hunspell/de_DE.dic cat /usr/share/hunspell/en_GB.dic>&2}
そして、あなたはそれを見るでしょう。 base64でエンコードされた行の一部はファイルの先頭にあり、一部は末尾にあり、デコードされていないstderrの内容は中央にあります。
まあ、下のアイデアを機能させることができないとしても(私はそう思います)、それはこのように機能することができると誤って信じる人々の反例として役立つかもしれません。
アイデア(または反例):
#!/bin/sh
exe()
{
echo out1
echo err1 >&2
echo out2
echo out3
echo err2 >&2
echo out4
echo err3 >&2
echo -n err4 >&2
}
r="$( { exe | base64 -w 0 ; } 2>&1 )"
echo RAW
printf '%s' "$r"
echo RAW
o="$( printf '%s' "$r" | tail -n 1 | base64 -d )"
e="$( printf '%s' "$r" | head -n -1 )"
unset r
echo
echo OUT
printf '%s' "$o"
echo OUT
echo
echo ERR
printf '%s' "$e"
echo ERR
(stderr-newline修正を含む):
$ ./ggg
RAW
err1
err2
err3
err4
b3V0MQpvdXQyCm91dDMKb3V0NAo=RAW
OUT
out1
out2
out3
out4OUT
ERR
err1
err2
err3
err4ERR
(少なくともDebianのダッシュとバッシュでは)
コマンド1)ステートフルな副作用がなく、2)計算コストが安い場合、最も簡単な解決策は2回実行することです。これは主に、ディスクが動作するかどうかわからないときにブートシーケンス中に実行されるコードに使用しました。私の場合、それは小さなsome_command
したがって、2回実行してもパフォーマンスに影響はなく、コマンドには副作用がありませんでした。
主な利点は、これがクリーンで読みやすいことです。ここでの解決策は非常に賢明ですが、より複雑な解決策を含むスクリプトを維持する必要があるのは嫌です。シナリオがうまく機能する場合は、シンプルで2回実行するアプローチをお勧めします。これは、はるかにクリーンで保守しやすいためです。
例:
output=$(getopt -o '' -l test: -- "$@")
errout=$(getopt -o '' -l test: -- "$@" 2>&1 >/dev/null)
if [[ -n "$errout" ]]; then
echo "Option Error: $errout"
fi
繰り返しますが、getoptには副作用がないため、これで十分です。私の親コードはプログラム全体でこれを100回未満しか呼び出さず、ユーザーは100 getopt呼び出しと200 getopt呼び出しに決して気付かないため、パフォーマンスセーフであることを知っています。