一時ファイルを使用せずに、stdoutとstderrを異なる変数に保存またはキャプチャすることは可能ですか?現在、実行時にstdoutout
とstderrを取得するためにこれを実行していますが、一時ファイルは避けたいと思います。err
some_command
error_file=$(mktemp)
out=$(some_command 2>$error_file)
err=$(< $error_file)
rm $error_file
一時ファイルを使用せずに、stdoutとstderrを異なる変数に保存またはキャプチャすることは可能ですか?現在、実行時にstdoutout
とstderrを取得するためにこれを実行していますが、一時ファイルは避けたいと思います。err
some_command
error_file=$(mktemp)
out=$(some_command 2>$error_file)
err=$(< $error_file)
rm $error_file
これは、異なる変数で stdout と stderr をキャッチするためのものです。 そのままにしておくだけでキャッチしたい場合は、より良い、より短い解決策があり
stderr
stdout
ます。
bash
ソリューションこのバージョンはサブシェルを使用し、tempfile
s なしで実行されます。(tempfile
サブシェルなしで実行されるバージョンについては、他の回答を参照してください。)
: 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
any の前に置くだけcommand args..
で完了です。
if cmd args..; then
になるものもありますif catch VAR1 VAR2 cmd args..; then
。本当に複雑なことは何もありません。
catch
厳密モードでも同じように機能します。唯一の注意点は、上記の例ではエラー コード 3 が返されることです。厳密モードでは、ERR トラップが呼び出されます。したがって、(0 だけでなく) 任意のエラー コードを返すことが予想されるコマンドを実行する場合は、以下に示すset -e
ように、リターン コードを変数にキャッチする必要があります。 && ret=$? || ret=$?
dummy()
{
echo "$3" >&2
echo "$2" >&1
return "$1"
}
catch stdout stderr dummy 3 $'\ndifficult\n data \n\n\n' $'\nother\n difficult \n data \n\n' && ret=$? || ret=$?
printf 'ret=%q\n' "$ret"
printf 'stdout=%q\n' "$stdout"
printf 'stderr=%q\n' "$stderr"
Q: どのように機能しますか?
ここでの他の回答からのアイデアを関数にラップするだけで、簡単に再利用できます。
catch()
基本的eval
に、2 つの変数を設定するために使用します。これはhttps://stackoverflow.com/a/18086548に似ています
の呼び出しを考えてみましょうcatch out err dummy 1 2a 3b
:
eval "$({
と はスキップしましょう__2="$(
。これについては後で説明します。
__1="$("$("${@:3}")"; } 2>&1;
実行し、後で使用するためにdummy 1 2a 3b
保存します。となります。また、外部キャッチが収集できるように、をにリダイレクトします。stdout
__1
__1
2a
stderr
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
もう一方の variable を出力するために既に使用されているため、再度使用されますout=2a
。
printf '( exit %q )' "$ret" >&2;
次に、適切な戻り値を設定するコードを出力します。変数に割り当てるには変数名が必要であり、変数名は の最初または2番目の引数として使用できないため、より良い方法は見つかりませんでしたcatch
。
最適化として、これら 2つを"$__2" "$ret"`printf
のように 1 つのものとして記述することもできます。printf '%s=%q\n( exit %q )
では、これまでのところ何がありますか?
stderr には次のように書かれています。
out=2a
err=3b
( exit 1 )
ここで、out
は から$1
、2a
は から、は から、はから、は からの戻りコードからです。stdout
dummy
err
$2
3b
stderr
dummy
1
dummy
%q
の形式でprintf
は、シェルが の場合に適切な (単一の) 引数を認識するように、引用に注意してくださいeval
。 2a
非常に3b
単純なので、文字通りコピーされます。
外側にeval "$({ ... } 2>&1 )";
:
これは上記のすべてを実行し、2 つの変数と を出力し、exit
それをキャッチし (したがって2>&1
)、 を使用して現在のシェルに解析しeval
ます。
このようにして、2 つの変数が設定され、戻りコードも同様に設定されます。
Q:eval
どちらが悪いのですか?それで安全ですか?
printf %q
安全なはずです。ただし、常に非常に注意する必要があります。ShellShock について考えてみてください。Q: バグ?
以下を除いて、明らかなバグは知られていません。
大きな出力をキャッチするには、すべてが変数になり、シェルによって逆解析される必要があるため、大きなメモリと CPU が必要です。だから賢く使ってください。
いつものように、最後のものだけでなく、$(echo $'\n\n\n\n')
すべての linefeed を飲み込みます。これは POSIX 要件です。LF を無傷にする必要がある場合は、次のレシピのように、出力に末尾の文字を追加し、後で削除するだけです (拡張子を見て、末尾x
が a のファイルを指すソフトリンクを読み取ることができます$'\n'
)。
target="$(readlink -e "$file")x"
target="${target%x}"
シェル変数はバイト NUL ( $'\0'
) を運ぶことはできません。stdout
またはで発生した場合、単に無視されますstderr
。
指定されたコマンドは、サブサブシェルで実行されます。したがって、にアクセスすること$PPID
も、シェル変数を変更することもできません。catch
ビルトインでさえ、シェル関数を使用できますが、シェル変数を変更することはできません (内部で実行されているすべてのもの$( .. )
はこれを行うことができないため)。したがって、現在のシェルで関数を実行して stderr/stdout をキャッチする必要がある場合は、tempfile
s を使用して通常の方法でこれを行う必要があります。(通常、シェルを中断しても残骸が残らないようにする方法はいくつかありますが、これは複雑であり、独自の回答に値します。)
Q: バッシュのバージョンは?
printf %q
)Q: これでもまだぎこちなく見えます。
ksh
は、それをよりきれいに行う方法を示しています。しかし、私は に慣れていないksh
ので、同様の簡単に再利用できるksh
.Q: なぜ使わないのksh
ですか?
bash
解決策だからQ: スクリプトを改善することができます
Q:タイプミスがあります。 : catch STDOUT STDERR cmd args..
読む# catch STDOUT STDERR cmd args..
:
れている間に現れます。bash -x
したがって、関数定義にタイプミスがあった場合に、パーサーがどこにあるかを確認できます。これは古いデバッグ トリックです。しかし、少し注意してください:
.;
編集:からシングルライナーを作成しやすくするために、さらにいくつか追加しました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 番号を使用することもできます。
このコマンドは、現在実行中のシェルに 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"
そのコードを実行する (そして vars セットを取得する) には、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|"
ジョナサンは答えを持っています。参考までに、これは 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 にリダイレクトされます (内部置換に適用されるように)。次に、 の stdoutout
が に割り当てられy
、リダイレクトされた stderr が によって取得されます。コマンド置換のサブシェルへx
の通常の損失はありません。y
出力をキャプチャするすべての構成では、プロデューサーをサブシェルに配置する必要があるため、他のシェルでは不可能です。この場合、サブシェルには割り当てが含まれます。
更新: mksh でもサポートされるようになりました。
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 -
}
これは、@madmurphy の非常に優れたソリューションがどのように機能するかを示す図です。
そして、ワンライナーのインデントされたバージョン:
catch() {
{
IFS=$'\n' read -r -d '' "$out_var";
IFS=$'\n' read -r -d '' "$err_var";
(IFS=$'\n' read -r -d '' _ERRNO_; return ${_ERRNO_});
}\
< <(
(printf '\0%s\0%d\0' \
"$(
(
(
(
{ ${3}; echo "${?}" 1>&3-; } | tr -d '\0' 1>&4-
) 4>&2- 2>&1- | tr -d '\0' 1>&4-
) 3>&1- | exit "$(cat)"
) 4>&1-
)" "${?}" 1>&2
) 2>&1
)
}
どうでしょう... =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"
ハックですが、このページのいくつかの提案よりもおそらく直感的な回避策の 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 が制限された形式であることがわかっている場合は、許可されたコンテンツと競合しないタグを考え出すことができます。
端的に言えば、答えは「いいえ」だと思います。キャプチャ$( ... )
は、変数への標準出力のみをキャプチャします。標準エラーを別の変数に取り込む方法はありません。だから、あなたが持っているものは、それが得られるのと同じくらいきれいです。
警告: (まだ?) 動作していません!
以下は、一時ファイルを作成せずに、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 呼び出しに気付かないため、パフォーマンスが安全であることはわかっています。