변수에 표준 오류를 저장하는 방법


182

다음과 같은 스크립트가 있다고 가정 해 봅시다.

쓸모없는

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

그리고 또 다른 쉘 스크립트가 있습니다 :

alsoUseless.sh

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

"This Is Error"또는 useless.sh의 다른 stderr를 변수로 캡처하고 싶습니다. 그것을 ERROR라고하자.

내가 stdout을 사용하고 있음에 주목하십시오. stdout을 계속 사용하고 싶습니다.이 경우 stderr을 stdout으로 리디렉션하는 것은 도움이되지 않습니다.

기본적으로 저는하고 싶습니다

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

그러나 그것은 분명히 작동하지 않습니다.

나도 할 수 있다는 걸 알아

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

그러나 그것은 추악하고 불필요합니다.

불행히도, 여기에 답변이 없으면 내가해야 할 일입니다.

다른 방법이 있기를 바라고 있습니다.

더 좋은 아이디어가 있습니까?


4
stdout을 정확히 사용하고 싶은 것은 무엇입니까? 콘솔에서 간단히 보시겠습니까? 아니면 출력을 캡처 / 리디렉션합니까? 콘솔에있는 경우 stdout을 콘솔로 리디렉션하고 stderr을 stdout으로 리디렉션하여 캡처합니다.ERROR=$(./useless.sh | sed 's/Output/Useless/' 2>&1 1>/dev/ttyX)
Tim Kersten

답변:


91

오류 파일을 캡처하는 것이 더 깔끔합니다.

ERROR=$(</tmp/Error)

쉘은 이것을 인식 cat하고 데이터를 얻기 위해 ' '를 실행할 필요가 없습니다 .

더 큰 질문은 어렵다. 쉬운 방법이 없다고 생각합니다. 오류를 표준 출력으로 리디렉션 할 수 있도록 전체 파이프 라인을 하위 셸에 빌드하여 최종 표준 출력을 파일로 전송해야합니다.

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

세미콜론이 필요하다는 것을 명심하십시오 (클래식 쉘-Bourne, Korn-아마도 Bash에서도). ' {}'는 동봉 된 명령에 대한 I / O 리디렉션을 수행합니다. 작성된 것처럼 오류 sed도 캡처합니다 .

경고 : 공식적으로 테스트되지 않은 코드-위험 부담으로 사용하십시오.


1
나는 내가 몰랐던 정말 미친 트릭이 있기를 바랐지만, 이것이 그렇게 보인다. 감사.
psycotica0 2016 년

9
표준 출력이 필요하지 않은 경우 /dev/null대신 출력으로 리디렉션 할 수 있습니다 outfile(나와 같은 경우 Google을 통해이 질문을 찾았으며 OP와 동일한 요구 사항이 없음)
Mark Eirich

2
임시 파일이없는 답변은 여기를 참조 하십시오 .
Tom Hale

1
다음 은 파일로 리디렉션하지 않고 수행하는 방법입니다. 그것은 교환을 재생 stdout하고 stderr앞뒤로. 그러나 주의 로, 여기에 말한다 : bash는, 그것은 더 나은 것 (3)가 사용되지 그 파일 기술자를 가정하지 " .
Golar Ramblar에게

69

alsoUseless.sh

이를 useless.sh통해와 같은 명령을 통해 스크립트 의 출력을 파이프하고 라는 변수에 sed저장할 수 있습니다. 파이프 결과는 표시 를 위해 전송 되거나 다른 명령으로 파이프됩니다.stderrerrorstdout

이 작업을 수행하는 데 필요한 리디렉션을 관리하기 위해 몇 가지 추가 파일 설명자를 설정합니다.

#!/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'를 사용하여 파일 디스크립터를 설정하고 닫는 것이 좋은 기술입니다. 스크립트가 바로 종료되면 닫기는 실제로 필요하지 않습니다.
Jonathan Leffler

3
변수 stderrstdout변수를 모두 어떻게 캡처 합니까?
Gingi

우수한. 이를 통해 dry_rundry-ran 명령이 다른 파일로 파이프되는지 여부에 관계없이 인수 반향과 실행 중에서 확실하게 선택할 수 있는 기능을 구현할 수 있습니다.
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 2016 년

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) Thanks !!
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를 변수로 잡기 위해 oneliner로 재사용 가능
  • 여전히 명령의 리턴 코드에 대한 액세스를 제공합니다
  • 임시 파일 디스크립터 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(아마도 버전 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출력 캡처에서 $(..)다시 stdout파일 디스크립터 3에 저장된 "외부"로 경로 재 지정합니다. 이는 stderr여전히 FD 1이 이전에 지정한 위치를 나타냅니다.$(..)
    • 3>&-그런 다음 더 이상 필요 command하지 않은 파일 디스크립터 3을 닫아 갑자기 알 수없는 열린 파일 디스크립터가 나타나지 않게합니다. 외부 셸에는 여전히 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>&-";
}

보안 정보 : 처음 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

@Stephan은 환호 합니다 . 여기 에서도 논의 되었습니다 . :)
Shadow Wizard is Ear For You

1
을 사용하는 것보다이 방법을 사용하는 것이 더 안전합니다 eval. 예를 들어 변수가 악의적 인 값으로 설정되어 있거나 대상 변수 이름에 해당 값이 포함되어 printf -v "$1" '%s' "$(<tmpFile)"있으면 임의의 코드를 실행할 위험이 없습니다 TMPDIR.
Charles Duffy

1
마찬가지로, rm -- "$tmpFile"보다 강력합니다 rm $tmpFile.
Charles Duffy

2

이것은 우아한 해결책이 있기를 바라는 흥미로운 문제입니다. 슬프게도, 나는 Leffler와 비슷한 솔루션으로 끝났지 만 가독성 향상을 위해 Bash 함수 내부에서 쓸모없는 전화를 걸 수 있다고 덧붙입니다.

#! / bin / bash

쓸모없는 기능 {
    /tmp/useless.sh | sed 's / 출력 / 무용 한 /'
}

오류 = $ (무효)
에코 $ 오류

다른 모든 종류의 출력 리디렉션은 임시 파일로 백업해야합니다.


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

명령 (여기서 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.


일반적인 아이디어에 대한 이 답변 덕분에 .


이 줄에 자세한 내용을 설명 할 수 있습니까? 이해하지 못했습니다 1> & $ tmp; {error = $ ({{ls -ld / XXXX / bin | tr o Z;} 1> & $ tmp;} 2> & 1); } {tmp}> & 1;
Thiago Conrado

1

이 게시물을 통해 본인의 목적에 맞는 비슷한 솔루션을 만들 수있었습니다.

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

그런 다음 MESSAGE가 빈 문자열이 아닌 한 다른 항목으로 전달합니다. 이것은 우리의 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에 의해 캡처됩니다은 $()오히려 콘솔로 이동하지만, 표준 에러보다 (의는 tee우리가 두 번째 출력으로 그것을 사용하므로) 여전히 콘솔로 이동합니다 tee특수 파일을 통해 /dev/fd/2이후 teefd에보다는 예상하는 파일 경로를 번호.

참고 : 그것은 한 줄에 많은 리디렉션이 있으며 순서가 중요합니다. 파이프 라인 끝에서 $()stdout을 잡고 tee파이프 라인 자체는 stdout을 stdin과 stdout을 교환 한 ./useless.sh후 stdin의 경로로 라우팅 tee합니다 ./useless.sh.

./useless.sh의 stdout 사용

OP는 여전히 (인쇄뿐만 아니라) stdout을 사용하고 싶다고 말했다 ./useless.sh | sed 's/Output/Useless/'.

stdout과 stderr을 교체하기 전에 문제가 없습니다. 함수 또는 파일 (alse-useless.sh)로 옮기고 위의 줄에서 ./useless.sh 대신 호출하는 것이 좋습니다.

그러나 stdout과 stderr을 캡처하려면 $()한 번에 하나만 수행하고 변수를 반환 할 수없는 하위 셸을 만들기 때문에 임시 파일로 폴백해야한다고 생각합니다 .


1

Tom Hale의 답변을 약간 반복하면서 리디렉션 요가를 더 쉽게 재사용 할 수있는 기능으로 래핑하는 것이 가능하다는 것을 알았습니다. 예를 들면 다음과 같습니다.

#!/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는 하위 셸에서 평가되고 할당되며 하위 셸의 할당은 상위 셸에 영향을 미치지 않습니다. (우분투 14.04 LTS뿐만 아니라 맥 OS X 10.10.1에서 테스트.)
조나단 레플러

Windows GitBash에서도 동일합니다. 따라서 작동하지 않습니다. ( GNU bash, version 4.4.12(1)-release (x86_64-pc-msys))
커비

SLE 11.4어느 쪽도 작동하지 않으며
@JonathanLeffler

이 코드는 질문에 대답 할 수 있지만,이 코드가 질문에 응답하는 이유 및 / 또는 방법에 대한 추가 컨텍스트를 제공하면 장기적인 가치가 향상됩니다.
β.εηοιτ.βε


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
}

린 (Lean) 제조에서 영감을 얻은 제품 :


관용적 솔루션은 내부에 과제를 제출합니다 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.