変数に標準エラーを格納する方法


182

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

役に立たない

echo "This Is Error" 1>&2
echo "This Is Output" 

そして、私は別のシェルスクリプトを持っています:

alsoUseless.sh

./useless.sh | sed 's/Output/Useless/'

「This Is Error」または他のstderrをuseless.shから変数にキャプチャしたい。ERRORと呼びましょう。

何かにstdoutを使用していることに注意してください。stdoutを使い続けたいので、この場合、stderrをstdoutにリダイレクトしても役に立ちません。

なので基本的には

./useless.sh 2> $ERROR | ...

しかし、それは明らかに機能しません。

私もできることを知っています

./useless.sh 2> /tmp/Error
ERROR=`cat /tmp/Error`

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

残念ながら、ここで答えが見つからない場合、それが私がしなければならないことです。

別の方法があるといいのですが。

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


4
stdoutを正確に使用したいのは何ですか?単にコンソールに表示しますか?それとも、その出力をキャプチャ/リダイレクトしていますか?それはコンソールにだけだ場合、あなたはそれをキャプチャするためにstdoutにコンソールとstderrに標準出力をリダイレクト:ERROR=$(./useless.sh | sed 's/Output/Useless/' 2>&1 1>/dev/ttyX)
ティムKersten氏

回答:


91

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

ERROR=$(</tmp/Error)

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

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

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

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

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


1
知らないクレイジーなトリックがあるのではないかと思っていたのですが、これだけのようです。ありがとう。
psycotica0 2009年

9
標準出力が必要ない場合は、/dev/null代わりにリダイレクトoutfileできます(私と同じように、この質問はGoogle経由で見つかり、OPと同じ要件はありません)
Mark Eirich

2
一時ファイルなしの回答については、こちらを参照してください
トム・ヘイル

1
ファイルにリダイレクトせずにそれを行う方法を次に示します。それはスワッピングstdoutstderr前後に遊んでいます。しかし、ここで述べられているように注意してくださいbashでは、ファイル記述子3が未使用であると想定しない方が良いでしょう
Golar Ramblar

69

alsoUseless.sh

これ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

4
'exec'を使用してファイル記述子を設定およびクローズすることは良いテクニックです。スクリプトが直後に終了する場合、クローズは実際には必要ありません。
ジョナサンレフラー、

3
変数stderrstdout変数の両方をどのようにキャプチャしますか?
ギンギ

優れた。これdry_runは、ドライランされているコマンドが他のファイルにパイプされているかどうかに関係なく、引数をエコーするか実行するかを確実に選択できる関数を実装するのに役立ちます。
Mihai Danila

1
@ t00bs:readパイプからの入力を受け入れません。他の手法を使用して、実証しようとしていることを実現できます。
追って通知があるまで一時停止。

2
error = $(./useless.sh | sed 's / Output / Useless /' 2>&1 1>&3)
Jocelyn

64

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

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

8
これが、例にパイプを含めた理由です。私はまだ標準出力が欲しいし、他のことをしたい、他の場所に行って欲しい。
psycotica0 2009年

stderrにのみ出力を送信するコマンドの場合、それをキャプチャする簡単な方法は、たとえばPY_VERSION="$(python --version 2>&1)"
John Mark

9

この質問には多くの重複があります。その多くは、stderr stdout 、および終了コードを同時にキャプチャしたくない、やや単純な使用シナリオです。

if result=$(useless.sh 2>&1); then
    stdout=$result
else
    rc=$?
    stderr=$result
fi

成功した場合は適切な出力が、失敗した場合はstderrに診断メッセージが表示されるという一般的なシナリオで機能します。

シェルの制御ステートメント$?は、内部ですでに調べていることに注意してください。だから何かのように見えます

cmd
if [ $? -eq 0 ], then ...

不器用で一義的な言い方です

if cmd; then ...

これは私のために働きました:my_service_status = $(service my_service status 2>&1)ありがとう!!
JRichardsz

6
# 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
command実際にはその名前のビルトインがあるので、ここでは悪い選択です。yourCommandより明確にするために、それなどにするかもしれません。
Charles Duffy

4

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

  • stderrを変数にキャッチするワンライナーとして再利用できます
  • それでもコマンドの戻りコードにアクセスできます
  • 一時ファイル記述子3を犠牲にします(もちろん、変更可能です)
  • そして、この一時ファイル記述子を内部コマンドに公開しません

あなたstderrが何かを捕まえたいならcommandvarあなたはそうすることができます

{ 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-functionにラップされます(おそらくバージョン3以降が必要ですlocal -n):

: 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出力キャプチャから離れて、ファイル記述子3に保存され$(..)た「外部」にリダイレクトstdoutします。なおstderr、FD 1が以前にポイントした場所を参照していることに注意してください。$(..)
    • 3>&-次に、不要になったファイル記述子3を閉じます。これにより、command不明なオープンファイル記述子が突然表示されることがなくなります。外側のシェルはまだFD 3が開いているが、command表示されないことに注意してください。
    • 後者のプログラムは重要lvmです。予期しないファイル記述子について不満を言うようなプログラムもあります。そしてlvm文句を言うstderr-私たちがキャプチャーしようとしているものだけ!

それに応じて調整すれば、このレシピで他のファイル記述子をキャッチできます。もちろんファイル記述子1を除きます(ここではリダイレクトロジックは間違っていますが、ファイル記述子1の場合はvar=$(command)通常どおりに使用できます)。

これはファイル記述子3を犠牲にすることに注意してください。そのファイル記述子が必要になった場合は、自由に数値を変更してください。ただし、一部のシェル(1980年代のもの)は99>&19その後に続く引数として理解する場合があることに注意してください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>&-";
}

セキュリティ上の注意:への最初の3つの引数catch-var-from-fd-by-fdは、サードパーティから取得してはなりません。常に「静的」な方法で明示的に指定してください。

だからノーノーノー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正しい数の引数を与えるのを忘れた場合の醜いエラーを防ぐための単なる方法です。おそらく、シェルを終了することは別の方法です(ただし、これによりコマンドラインからのテストが困難になります)。
  • ルーチンは、理解しやすいように書かれています。関数を必要としないように書き換えることができますがexec、その場合は本当に醜くなります。
  • このルーチンbashは、を必要としないように、同様に書き換えることができますlocal -n。ただし、ローカル変数は使用できず、非常に醜くなります。
  • また、evalsは安全に使用されていることに注意してください。通常evalは危険と見なされます。ただし、この場合は"$@"(任意のコマンドを実行するために)を使用するのと同じくらい悪です。ただし、ここに示すように、正確で正しい引用を使用してください(そうしないと、非常に危険になります)。

3

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

#
# $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-

それはありません一時ファイルを使用します。しかし、少なくとも醜いものは関数にラップされています。


@ShadowWizard私の側には少し疑問があります。フランス語では、コロンの前に通常スペースが付きます。私は誤ってこれと同じルールを英語の回答に適用します。これを確認した後、私は再びこの間違いをするつもりはありません。
ステファン

@Stephanは乾杯、これもここで議論されました。:)
Shadow WizardはEar For You

1
これを行うには、を使用するよりも安全な方法がありますeval。たとえばprintf -v "$1" '%s' "$(<tmpFile)"TMPDIR変数が悪意のある値に設定されている場合(または宛先変数名にそのような値が含まれている場合)は、任意のコードを実行する危険はありません。
Charles Duffy

1
同様に、rm -- "$tmpFile"より堅牢ですrm $tmpFile
Charles Duffy

2

これは興味深い問題で、エレガントな解決策があったことを期待していました。悲しいことに、私はレフラー氏に似た解決策に終わりますが、読みやすさを向上させるためにBash関数内から役に立たないものを呼び出すことができることを付け加えます。

#!/ bin / bash

役に立たない関数{
    /tmp/useless.sh | sed 's / Output / Useless /'
}

ERROR = $(役に立たない)
エコー$ ERROR

他のすべての種類の出力リダイレクトは、一時ファイルでバックアップする必要があります。


2

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

コマンドのSTDOUT(ここls)のパイピングは最も内側の内部で行われることに注意してください{ }。単純なコマンド(たとえば、パイプではない)を実行している場合は、これらの内側の波括弧を削除できます。

パイプを使用するとサブシェルがbashzshで作成されるため、コマンドの外部にパイプすることはできません。また、サブシェルの変数への割り当ては、現在のシェルでは使用できません。

バッシュ

では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


一般的な考えに対するこの回答に感謝します。


この行を詳細に説明できますか?理解できませんでした1>&$ tmp; {error = $({{ls -ld / XXXX / bin | tr o Z;} 1>&$ tmp;} 2>&1); } {tmp}>&1;
チアゴコンラード

1

この投稿は、私自身の目的のために同様の解決策を見つけるのに役立ちました:

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

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


1

stderrのキャプチャと印刷

ERROR=$( ./useless.sh 3>&1 1>&2 2>&3 | tee /dev/fd/2 )

壊す

を使用$()してstdoutをキャプチャできますが、代わりにstderrをキャプチャします。したがって、stdoutとstderrを交換します。標準スワップアルゴリズムの一時ストレージとしてfd 3を使用します。

キャプチャして印刷したい場合はtee、複製を作成してください。この場合、の出力はコンソールに行くのではなくteeによってキャプチャされます$()が、stderr(of tee)はまだコンソールに行くので、fdではなくファイルパスを想定しているのでtee、それを特殊ファイル経由の2番目の出力として使用します。数。/dev/fd/2tee

注:これは、1行のリダイレクトが非常に多く、順序が重要です。 $()teeパイプラインの最後でstdoutを取得し、パイプライン自体がstdout ./useless.shをのstdinにルーティングtee./useless.shます。その後、stdinとstdoutをにスワップします。

./useless.shのstdoutを使用する

OPは、(印刷だけでなく)stdoutを引き続き使用したいと述べた./useless.sh | sed 's/Output/Useless/'

stdoutとstderrを入れ替える前に、問題はありません。私はそれを関数またはファイル(これもuseless.sh)に移動して、上記の行の./useless.shの代わりに呼び出すことをお勧めします。

ただし、stdoutとstderrをキャプチャする場合は、一時ファイルに$()頼る必要があると思います。これは、一度に1つしか実行せず、サブシェルを作成して変数を返すことができないためです。


1

トムヘイルの答えを少し繰り返して、リダイレクションヨガを関数にラップして再利用を容易にすることが可能だとわかりました。例えば:

#!/bin/sh

capture () {
    { captured=$( { { "$@" ; } 1>&3 ; } 2>&1); } 3>&1
}

# Example usage; capturing dialog's output without resorting to temp files
# was what motivated me to search for this particular SO question
capture dialog --menu "Pick one!" 0 0 0 \
        "FOO" "Foo" \
        "BAR" "Bar" \
        "BAZ" "Baz"
choice=$captured

clear; echo $choice

これをさらに簡略化することはほぼ確実に可能です。特に徹底的なテストはしていませんが、bashとkshの両方で動作するようです。


0

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

$ .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にないデータストリームを操作する機能が購入されましたが、残念ながら、私はそれを思いどおりに操作することができません。


1
実行すると./useless.sh 2> >( ERROR=$( cat <() ); echo "$ERROR" )、の出力が表示されますERROR。問題は、プロセス置換がサブシェルで実行されるため、サブシェルで設定された値が親シェルに影響を与えないことです。
Jonathan Leffler、2014

0
$ b=$( ( a=$( (echo stdout;echo stderr >&2) ) ) 2>&1 )
$ echo "a=>$a b=>$b"
a=>stdout b=>stderr

3
これは良い考えのように見えますが、Mac OSX 10.8.5では印刷されますa=> b=>stderr
Heath Borders

3
@HeathBordersに同意します。これは表示される出力を生成しません。ここでの問題は、a評価されてサブシェルで割り当てられ、サブシェルでの割り当てが親シェルに影響を与えないことです。(Ubuntu 14.04 LTSおよびMac OS X 10.10.1でテスト済み)
Jonathan Leffler

Windows GitBashでも同じです。したがって、機能しません。(GNU bash, version 4.4.12(1)-release (x86_64-pc-msys)
カービー

SLE 11.4どちらでも機能せず、@ JonathanLefflerによって記述された効果を生成します
smarber

このコードは質問に答えることがありますが、このコードが質問に答える理由や方法に関する追加のコンテキストを提供すると、長期的な価値が向上します。
β.εηοιτ.βε


0

以下のためにエラープルーフあなたのコマンドを:

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
}

リーン生産に触発されました:


慣用的な解決策は、内の割り当てを評価することifです。別のソリューションを投稿させてください。
tripleee


0

YellowAppleの答えの改善:

これはstderrを任意の変数にキャプチャするBash関数です

stderr_capture_example.sh

#!/usr/bin/env bash

# Capture stderr from a command to a variable while maintaining stdout
# @Args:
# $1: The variable name to store the stderr output
# $2: Vararg command and arguments
# @Return:
# The Command's Returnn-Code or 2 if missing arguments
function capture_stderr {
  [ $# -lt 2 ] && return 2
  local stderr="$1"
  shift
  {
    printf -v "$stderr" '%s' "$({ "$@" 1>&3; } 2>&1)"
  } 3>&1
}

# Testing with a call to erroring ls
LANG=C capture_stderr my_stderr ls "$0" ''

printf '\nmy_stderr contains:\n%s' "$my_stderr"

テスト:

bash stderr_capture_example.sh

出力:

 stderr_capture_example.sh

my_stderr contains:
ls: cannot access '': No such file or directory

この関数は、返されたdialogコマンドの選択肢をキャプチャするために使用できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.