web-dev-qa-db-ja.com

変数に標準エラーを保存する方法

次のようなスクリプトがあるとします。

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`

しかし、それはくて不必要です。

残念ながら、答えがここに表示されない場合、それが私がしなければならないことです。

私は別の方法があることを望んでいます。

誰より良いアイデアがありますか?

158
psycotica0

したがって、エラーファイルをキャプチャする方が適切です。

ERROR=$(</tmp/Error)

シェルはこれを認識し、データを取得するために「cat」を実行する必要はありません。

大きな質問は難しいです。簡単な方法はないと思います。パイプライン全体をサブシェルに組み込み、最終的にその標準出力をファイルに送信して、エラーを標準出力にリダイレクトできるようにする必要があります。

ERROR=$( { ./useless.sh | sed s/Output/Useless/ > outfile; } 2>&1 )

セミコロンが必要であることに注意してください(古典的なシェル-Bourne、Korn-確かに;たぶんBashでも)。 '{}'は、囲まれたコマンドに対するI/Oリダイレクトを行います。書かれているように、それもsedからエラーをキャプチャします。

警告:正式にテストされていないコード-自己責任で使用してください。

75

また、Useless.sh

これにより、useless.shスクリプトの出力をsedなどのコマンドでパイプ処理し、stderrerrorという名前の変数に保存できます。パイプの結果は、表示のために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
63

Stderrをstdoutにリダイレクトし、stdoutを/ dev/nullにリダイレクトしてから、バックティックまたは$()を使用してリダイレクトされたstderrをキャプチャします。

ERROR=$(./useless.sh 2>&1 >/dev/null)
56
Chas. Owens

この質問には多くの重複があります。その多くは、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 ...
8
tripleee
# 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}
5
human9

ここに私がそれをした方法があります:

#
# $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一時ファイルを使用します。しかし、少なくともいものは関数に包まれています。

3
tfga

読者の利益のために、このレシピはこちら

  • stderrを変数に取り込むためのonelinerとして再利用できます
  • まだコマンドのリターンコードにアクセスできます
  • 一時ファイル記述子3を犠牲にします(もちろん、これはユーザーが変更できます)
  • そして、この一時ファイル記述子を内部コマンドに公開しません

stderrcommandvarにキャッチしたい場合

{ 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>&1stderrを出力キャプチャにリダイレクトします$(..)
    • 1>&3stdoutを出力キャプチャからリダイレクトします$(..)は「外部」に戻りますstdoutはファイル記述子3に保存されました。stderrは引き続きFD 1が前に指していた場所:出力キャプチャ$(..)
    • 3>&-は、不要になったファイル記述子3を閉じます。その結果、commandに未知の開いているファイル記述子が突然表示されることはありません。外側のシェルではまだFD 3が開いていますが、commandには表示されないことに注意してください。
    • lvmのような一部のプログラムは、予期しないファイル記述子について文句を言うので、後者は重要です。そしてlvmstderrに文句を言います-まさにキャプチャしようとしているものです!

それに応じて適応すれば、このレシピで他のファイル記述子をキャッチできます。もちろん、ファイル記述子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以外にも書き換えることができます。ただし、ローカル変数は使用できず、非常に見苦しくなります。
  • また、evalsは安全な方法で使用されることに注意してください。通常、evalは危険と見なされます。ただし、この場合は、"$@"(任意のコマンドを実行する)を使用するよりも害はありません。ただし、ここに示すように正確で正しい引用符を使用してください(そうでない場合は非常に非常に危険になります)。
2
Tino

これは興味深い問題であり、エレガントな解決策があればいいのですが。残念ながら、Mr。Lefflerに似たソリューションになりますが、読みやすさを向上させるために、Bash関数内から無駄な呼び出しを行うことができることを追加します。

#!/ bin/bash 
 
 function useless {
 /tmp/useless.sh | sed 's/Output/Useless /'
}

 ERROR = $(役に立たない)
 echo $ ERROR 

他のすべての種類の出力リダイレクトは、一時ファイルを使用する必要があります。

2
FD Gonthier
$ b=$( ( a=$( (echo stdout;echo stderr >&2) ) ) 2>&1 )
$ echo "a=>$a b=>$b"
a=>stdout b=>stderr
1
Mario Wolff

この投稿は、私自身の目的のために同様の解決策を思いつくのに役立ちました。

MESSAGE=`{ echo $ERROR_MESSAGE | format_logs.py --level=ERROR; } 2>&1`

次に、MESSAGEが空の文字列でない限り、他のものに渡します。これにより、format_logs.pyが何らかのpython例外で失敗した場合に通知されます。

1
palmbardier

一時ファイルの使用をバイパスする場合は、プロセス置換を使用できる場合があります。まだうまくいきませんでした。これは私の最初の試みでした:

$ .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に含まれていないデータストリームを操作できるようになりました。残念ながら、思い通りに操作することはできないようです。

0

Zshの場合:

{ . ./useless.sh > /dev/tty } 2>&1 | read ERROR
$ echo $ERROR
( your message )
0
Ray Andrews

POSIX

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

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では機能しないことに注意してください。


この回答 に感謝します。

0
Tom Hale

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
0
Karl Morrison