次のようなスクリプトがあるとします。
useless.sh
echo "This Is Error" 1>&2
echo "This Is Output"
また、別のシェルスクリプトがあります。
また、Useless.sh
./useless.sh | sed 's/Output/Useless/'
「This Is Error」またはuseless.shの他のstderrを変数にキャプチャしたい。それをエラーと呼びましょう。
何かにstdoutを使用していることに注意してください。私はstdoutを使い続けたいので、この場合、stderrをstdoutにリダイレクトすることは役に立ちません。
だから、基本的に、私はやりたい
./useless.sh 2> $ERROR | ...
しかし、それは明らかに機能しません。
私もできることを知っています
./useless.sh 2> /tmp/Error
ERROR=`cat /tmp/Error`
しかし、それはくて不必要です。
残念ながら、答えがここに表示されない場合、それが私がしなければならないことです。
私は別の方法があることを望んでいます。
誰より良いアイデアがありますか?
したがって、エラーファイルをキャプチャする方が適切です。
ERROR=$(</tmp/Error)
シェルはこれを認識し、データを取得するために「cat
」を実行する必要はありません。
大きな質問は難しいです。簡単な方法はないと思います。パイプライン全体をサブシェルに組み込み、最終的にその標準出力をファイルに送信して、エラーを標準出力にリダイレクトできるようにする必要があります。
ERROR=$( { ./useless.sh | sed s/Output/Useless/ > outfile; } 2>&1 )
セミコロンが必要であることに注意してください(古典的なシェル-Bourne、Korn-確かに;たぶんBashでも)。 '{}
'は、囲まれたコマンドに対するI/Oリダイレクトを行います。書かれているように、それもsed
からエラーをキャプチャします。
警告:正式にテストされていないコード-自己責任で使用してください。
これにより、useless.sh
スクリプトの出力をsed
などのコマンドでパイプ処理し、stderr
をerror
という名前の変数に保存できます。パイプの結果は、表示のためにstdout
に送信されるか、別のコマンドにパイプされます。
これを行うために必要なリダイレクトを管理するために、追加のファイル記述子をいくつかセットアップします。
#!/bin/bash
exec 3>&1 4>&2 #set up extra file descriptors
error=$( { ./useless.sh | sed 's/Output/Useless/' 2>&4 1>&3; } 2>&1 )
echo "The message is \"${error}.\""
exec 3>&- 4>&- # release the extra file descriptors
Stderrをstdoutにリダイレクトし、stdoutを/ dev/nullにリダイレクトしてから、バックティックまたは$()
を使用してリダイレクトされたstderrをキャプチャします。
ERROR=$(./useless.sh 2>&1 >/dev/null)
この質問には多くの重複があります。その多くは、stderr and stdout andの終了コードをすべてキャプチャしたくないという、少し単純な使用シナリオです。同じ時間。
if result=$(useless.sh 2>&1); then
stdout=$result
else
rc=$?
stderr=$result
fi
成功した場合は適切な出力を、失敗した場合はstderrの診断メッセージを期待する一般的なシナリオで機能します。
シェルの制御ステートメントは、すでに内部で$?
を調べていることに注意してください。のようなものは何でも
cmd
if [ $? -eq 0 ], then ...
不器用な、一風変わった言い方です
if cmd; then ...
# command receives its input from stdin.
# command sends its output to stdout.
exec 3>&1
stderr="$(command </dev/stdin 2>&1 1>&3)"
exitcode="${?}"
echo "STDERR: $stderr"
exit ${exitcode}
ここに私がそれをした方法があります:
#
# $1 - name of the (global) variable where the contents of stderr will be stored
# $2 - command to be executed
#
captureStderr()
{
local tmpFile=$(mktemp)
$2 2> $tmpFile
eval "$1=$(< $tmpFile)"
rm $tmpFile
}
使用例:
captureStderr err "./useless.sh"
echo -$err-
It does一時ファイルを使用します。しかし、少なくともいものは関数に包まれています。
読者の利益のために、このレシピはこちら
stderr
のcommand
をvar
にキャッチしたい場合
{ var="$( { command; } 2>&1 1>&3 3>&- )"; } 3>&1;
その後、あなたはそれをすべて持っています:
echo "command gives $? and stderr '$var'";
command
が単純な場合(a | b
のようなものではない)、内側の{}
を残すことができます。
{ var="$(command 2>&1 1>&3 3>&-)"; } 3>&1;
簡単に再利用可能なbash
-関数にラップされます(おそらくlocal -n
にはバージョン3以降が必要です):
: catch-stderr var cmd [args..]
catch-stderr() { local -n v="$1"; shift && { v="$("$@" 2>&1 1>&3 3>&-)"; } 3>&1; }
説明:
local -n
エイリアス "$ 1"(catch-stderr
の変数)3>&1
はファイル記述子3を使用してstdoutポイントを保存します{ command; }
(または "$ @")は、出力キャプチャ内でコマンドを実行します$(..)
2>&1
はstderr
を出力キャプチャにリダイレクトします$(..)
1>&3
はstdout
を出力キャプチャからリダイレクトします$(..)
は「外部」に戻りますstdout
はファイル記述子3に保存されました。stderr
は引き続きFD 1が前に指していた場所:出力キャプチャ$(..)
3>&-
は、不要になったファイル記述子3を閉じます。その結果、command
に未知の開いているファイル記述子が突然表示されることはありません。外側のシェルではまだFD 3が開いていますが、command
には表示されないことに注意してください。lvm
のような一部のプログラムは、予期しないファイル記述子について文句を言うので、後者は重要です。そしてlvm
はstderr
に文句を言います-まさにキャプチャしようとしているものです!それに応じて適応すれば、このレシピで他のファイル記述子をキャッチできます。もちろん、ファイル記述子1を除きます(ここではリダイレクトロジックが間違っていますが、ファイル記述子1の場合は、通常どおりvar=$(command)
を使用できます)。
これにより、ファイル記述子3が犠牲になることに注意してください。そのファイル記述子が必要になった場合は、お気軽に数値を変更してください。ただし、一部のシェル(1980年代以降)では、99>&1
を引数9
に続けて9>&1
を理解する場合があることに注意してください(bash
の場合は問題ありません)。
また、このFD 3を変数を介して構成することは特に簡単ではないことに注意してください。これにより、非常に読みにくくなります。
: catch-var-from-fd-by-fd variable fd-to-catch fd-to-sacrifice command [args..]
catch-var-from-fd-by-fd()
{
local -n v="$1";
local fd1="$2" fd2="$3";
shift 3 || return;
eval exec "$fd2>&1";
v="$(eval '"$@"' "$fd1>&1" "1>&$fd2" "$fd2>&-")";
eval exec "$fd2>&-";
}
セキュリティに関する注記:
catch-var-from-fd-by-fd
の最初の3つの引数は、サードパーティのものであってはなりません。常に「静的」な方法で明示的に提供してください。ノーノーノー
catch-var-from-fd-by-fd $var $fda $fdb $command
、これを絶対にしないでください!たまたま変数変数名を渡す場合は、少なくとも次のようにしてください:
local -n var="$var"; catch-var-from-fd-by-fd var 3 5 $command
それでも、すべてのエクスプロイトから保護されるわけではありませんが、少なくとも一般的なスクリプトエラーの検出と回避には役立ちます。
ノート:
catch-var-from-fd-by-fd var 2 3 cmd..
はcatch-stderr var cmd..
と同じですshift || return
は、正しい数の引数を与えるのを忘れた場合にinいエラーを防ぐための単なる方法です。おそらく、シェルを終了することは別の方法でしょう(ただし、これによりコマンドラインからテストするのが難しくなります)。exec
を必要としないように関数を書き換えることができますが、実際にはいものになります。local -n
が不要になるように、bash
以外にも書き換えることができます。ただし、ローカル変数は使用できず、非常に見苦しくなります。eval
sは安全な方法で使用されることに注意してください。通常、eval
は危険と見なされます。ただし、この場合は、"$@"
(任意のコマンドを実行する)を使用するよりも害はありません。ただし、ここに示すように正確で正しい引用符を使用してください(そうでない場合は非常に非常に危険になります)。これは興味深い問題であり、エレガントな解決策があればいいのですが。残念ながら、Mr。Lefflerに似たソリューションになりますが、読みやすさを向上させるために、Bash関数内から無駄な呼び出しを行うことができることを追加します。
#!/ bin/bash function useless { /tmp/useless.sh | sed 's/Output/Useless /' } ERROR = $(役に立たない) echo $ ERROR
他のすべての種類の出力リダイレクトは、一時ファイルを使用する必要があります。
$ b=$( ( a=$( (echo stdout;echo stderr >&2) ) ) 2>&1 )
$ echo "a=>$a b=>$b"
a=>stdout b=>stderr
この投稿は、私自身の目的のために同様の解決策を思いつくのに役立ちました。
MESSAGE=`{ echo $ERROR_MESSAGE | format_logs.py --level=ERROR; } 2>&1`
次に、MESSAGEが空の文字列でない限り、他のものに渡します。これにより、format_logs.pyが何らかのpython例外で失敗した場合に通知されます。
一時ファイルの使用をバイパスする場合は、プロセス置換を使用できる場合があります。まだうまくいきませんでした。これは私の最初の試みでした:
$ .useless.sh 2> >( ERROR=$(<) )
-bash: command substitution: line 42: syntax error near unexpected token `)'
-bash: command substitution: line 42: `<)'
それから私は試した
$ ./useless.sh 2> >( ERROR=$( cat <() ) )
This Is Output
$ echo $ERROR # $ERROR is empty
しかしながら
$ ./useless.sh 2> >( cat <() > asdf.txt )
This Is Output
$ cat asdf.txt
This Is Error
そのため、プロセスの置換は一般的に正しいことです...残念ながら、>( )
の中にSTDINを$()
でラップして変数にキャプチャしようとすると、 $()
の内容を失います。これは、$()
が、親プロセスが所有する/ dev/fd内のファイル記述子にアクセスできなくなったサブプロセスを起動するためだと思います。
プロセスの置換により、STDERRに含まれていないデータストリームを操作できるようになりました。残念ながら、思い通りに操作することはできないようです。
Zshの場合:
{ . ./useless.sh > /dev/tty } 2>&1 | read ERROR
$ echo $ERROR
( your message )
STDERRは、いくつかのリダイレクトマジックでキャプチャできます。
$ { error=$( { { ls -ld /XXXX /bin | tr o Z ; } 1>&3 ; } 2>&1); } 3>&1
lrwxrwxrwx 1 rZZt rZZt 7 Aug 22 15:44 /bin -> usr/bin/
$ echo $error
ls: cannot access '/XXXX': No such file or directory
コマンド(ここではls
)のSTDOUTのパイピングは、最も内側の{
}
内で行われることに注意してください。単純なコマンド(たとえば、パイプではない)を実行している場合、これらの内側の中括弧を削除できます。
パイピングはbash
およびzsh
にサブシェルを作成するため、コマンドの外部にパイプすることはできません。また、サブシェル内の変数への割り当ては、現在のシェルでは使用できません。
bash
では、ファイル記述子3が未使用であると仮定しない方が良いでしょう:
{ error=$( { { ls -ld /XXXX /bin | tr o Z ; } 1>&$tmp ; } 2>&1); } {tmp}>&1;
exec {tmp}>&- # With this syntax the FD stays open
これはzsh
では機能しないことに注意してください。
この回答 に感謝します。
error proofingコマンドの場合:
execute [INVOKING-FUNCTION] [COMMAND]
execute () {
function="${1}"
command="${2}"
error=$(eval "${command}" 2>&1 >"/dev/null")
if [ ${?} -ne 0 ]; then
echo "${function}: ${error}"
exit 1
fi
}
リーン製造業に触発された:
{ ERROR=$(./useless.sh 2>&1 1>&$out); } {out}>&1
echo "-"
echo $ERROR
生産する:
This Is Output
-
This Is Error