변수에 표준 오류를 저장하는 방법
다음과 같은 스크립트가 있다고 가정 해 봅시다.
쓸모없는
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`
그러나 그것은 추악하고 불필요합니다.
불행히도, 여기에 답변이 없으면 내가해야 할 일입니다.
다른 방법이 있기를 바라고 있습니다.
더 좋은 아이디어가 있습니까?
오류 파일을 캡처하는 것이 더 깔끔합니다.
ERROR=$(</tmp/Error)
쉘은 이것을 인식 cat
하고 데이터를 얻기 위해 ' '를 실행할 필요가 없습니다 .
더 큰 질문은 어렵다. 쉬운 방법이 없다고 생각합니다. 오류를 표준 출력으로 리디렉션 할 수 있도록 전체 파이프 라인을 하위 셸에 빌드하여 최종 표준 출력을 파일로 전송해야합니다.
ERROR=$( { ./useless.sh | sed s/Output/Useless/ > outfile; } 2>&1 )
세미콜론이 필요하다는 것을 명심하십시오 (클래식 쉘-Bourne, Korn-아마도 Bash에서도). ' {}
'는 동봉 된 명령에 대한 I / O 리디렉션을 수행합니다. 작성된 것처럼 오류 sed
도 캡처합니다 .
경고 : 공식적으로 테스트되지 않은 코드-위험 부담으로 사용하십시오.
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
stderr을 stdout으로 리디렉션하고 stdout을 / dev / null로 $()
리디렉션 한 다음 백틱을 사용하거나 리디렉션 된 stderr를 캡처합니다.
ERROR=$(./useless.sh 2>&1 >/dev/null)
이 질문에 대한 중복이 많이 있으며, 대부분은 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 ...
# 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-
그것은 않는 임시 파일을 사용합니다. 그러나 적어도 못생긴 물건은 기능에 싸여 있습니다.
이것은 우아한 해결책이 있기를 바라는 흥미로운 문제입니다. 슬프게도 Leffler와 비슷한 솔루션으로 끝났지 만 가독성 향상을 위해 Bash 함수 내부에서 쓸모없는 전화를 걸 수 있다고 덧붙입니다.
#! / bin / bash 쓸모없는 기능 { /tmp/useless.sh | sed 's / 출력 / 무용 한 /' } 오류 = $ (무효) 에코 $ 오류
다른 모든 종류의 출력 리디렉션은 임시 파일로 백업해야합니다.
독자의 이익을 위해 여기 에이 레시피
- stderr를 변수로 잡기 위해 oneliner로 재사용 가능
- 여전히 명령의 리턴 코드에 대한 액세스를 제공합니다
- 임시 파일 디스크립터 3을 희생합니다 (물론 변경 가능)
- 그리고이 임시 파일 설명자를 내부 명령에 노출시키지 않습니다
If you want to catch stderr
of some command
into var
you can do
{ var="$( { command; } 2>&1 1>&3 3>&- )"; } 3>&1;
Afterwards you have it all:
echo "command gives $? and stderr '$var'";
If command
is simple (not something like a | b
) you can leave the inner {}
away:
{ var="$(command 2>&1 1>&3 3>&-)"; } 3>&1;
Wrapped into an easy reusable bash
-function (probably needs version 3 and above for local -n
):
: catch-stderr var cmd [args..]
catch-stderr() { local -n v="$1"; shift && { v="$("$@" 2>&1 1>&3 3>&-)"; } 3>&1; }
Explained:
local -n
aliases "$1" (which is the variable forcatch-stderr
)3>&1
uses file descriptor 3 to save there stdout points{ command; }
(or "$@") then executes the command within the output capturing$(..)
- Please note that the exact order is important here (doing it the wrong way shuffles the file descriptors wrongly):
2>&1
redirectsstderr
to the output capturing$(..)
1>&3
redirectsstdout
away from the output capturing$(..)
back to the "outer"stdout
which was saved in file descriptor 3. Note thatstderr
still refers to where FD 1 pointed before: To the output capturing$(..)
3>&-
then closes the file descriptor 3 as it is no more needed, such thatcommand
does not suddenly has some unknown open file descriptor showing up. Note that the outer shell still has FD 3 open, butcommand
will not see it.- The latter is important, because some programs like
lvm
complain about unexpected file descriptors. Andlvm
complains tostderr
- just what we are going to capture!
You can catch any other file descriptor with this recipe, if you adapt accordingly. Except file descriptor 1 of course (here the redirection logic would be wrong, but for file descriptor 1 you can just use var=$(command)
as usual).
Note that this sacrifices file descriptor 3. If you happen to need that file descriptor, feel free to change the number. But be aware, that some shells (from the 1980s) might understand 99>&1
as argument 9
followed by 9>&1
(this is no problem for bash
).
Also note that it is not particluar easy to make this FD 3 configurable through a variable. This makes things very unreadable:
: 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>&-";
}
Security note: The first 3 arguments to
catch-var-from-fd-by-fd
must not be taken from a 3rd party. Always give them explicitly in a "static" fashion.So no-no-no
catch-var-from-fd-by-fd $var $fda $fdb $command
, never do this!If you happen to pass in a variable variable name, at least do it as follows:
local -n var="$var"; catch-var-from-fd-by-fd var 3 5 $command
This still will not protect you against every exploit, but at least helps to detect and avoid common scripting errors.
Notes:
catch-var-from-fd-by-fd var 2 3 cmd..
is the same ascatch-stderr var cmd..
shift || return
is just some way to prevent ugly errors in case you forget to give the correct number of arguments. Perhaps terminating the shell would be another way (but this makes it hard to test from commandline).- The routine was written such, that it is more easy to understand. One can rewrite the function such that it does not need
exec
, but then it gets really ugly. - This routine can be rewritten for non-
bash
as well such that there is no need forlocal -n
. However then you cannot use local variables and it gets extremely ugly! - Also note that the
eval
s are used in a safe fashion. Usuallyeval
is considerered dangerous. However in this case it is no more evil than using"$@"
(to execute arbitrary commands). However please be sure to use the exact and correct quoting as shown here (else it becomes very very dangerous).
$ b=$( ( a=$( (echo stdout;echo stderr >&2) ) ) 2>&1 )
$ echo "a=>$a b=>$b"
a=>stdout b=>stderr
This post helped me come up with a similar solution for my own purposes:
MESSAGE=`{ echo $ERROR_MESSAGE | format_logs.py --level=ERROR; } 2>&1`
Then as long as our MESSAGE is not an empty string, we pass it on to other stuff. This will let us know if our format_logs.py failed with some kind of python exception.
Capture AND Print stderr
ERROR=$( ./useless.sh 3>&1 1>&2 2>&3 | tee /dev/fd/2 )
Breakdown
You can use $()
to capture stdout, but you want to capture stderr instead. So you swap stdout and stderr. Using fd 3 as the temporary storage in the standard swap algorithm.
If you want to capture AND print use tee
to make a duplicate. In this case the output of tee
will be captured by $()
rather than go to the console, but stderr(of tee
) will still go to the console so we use that as the second output for tee
via the special file /dev/fd/2
since tee
expects a file path rather than a fd number.
NOTE: That is an awful lot of redirections in a single line and the order matters. $()
is grabbing the stdout of tee
at the end of the pipeline and the pipeline itself routes stdout of ./useless.sh
to the stdin of tee
AFTER we swapped stdin and stdout for ./useless.sh
.
Using stdout of ./useless.sh
The OP said he still wanted to use (not just print) stdout, like ./useless.sh | sed 's/Output/Useless/'
.
No problem just do it BEFORE swapping stdout and stderr. I recommend moving it into a function or file (also-useless.sh) and calling that in place of ./useless.sh in the line above.
However, if you want to CAPTURE stdout AND stderr, then I think you have to fall back on temporary files because $()
will only do one at a time and it makes a subshell from which you cannot return variables.
A simple solution
{ ERROR=$(./useless.sh 2>&1 1>&$out); } {out}>&1
echo "-"
echo $ERROR
Will produce:
This Is Output
-
This Is Error
If you want to bypass the use of a temporary file you may be able to use process substitution. I haven't quite gotten it to work yet. This was my first attempt:
$ .useless.sh 2> >( ERROR=$(<) )
-bash: command substitution: line 42: syntax error near unexpected token `)'
-bash: command substitution: line 42: `<)'
Then I tried
$ ./useless.sh 2> >( ERROR=$( cat <() ) )
This Is Output
$ echo $ERROR # $ERROR is empty
However
$ ./useless.sh 2> >( cat <() > asdf.txt )
This Is Output
$ cat asdf.txt
This Is Error
So the process substitution is doing generally the right thing... unfortunately, whenever I wrap STDIN inside >( )
with something in $()
in an attempt to capture that to a variable, I lose the contents of $()
. I think that this is because $()
launches a sub process which no longer has access to the file descriptor in /dev/fd which is owned by the parent process.
Process substitution has bought me the ability to work with a data stream which is no longer in STDERR, unfortunately I don't seem to be able to manipulate it the way that I want.
In zsh:
{ . ./useless.sh > /dev/tty } 2>&1 | read ERROR
$ echo $ERROR
( your message )
POSIX
STDERR can be captured with some redirection magic:
$ { 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
Note that piping of STDOUT of the command (here ls
) is done inside the innermost {
}
. If you're executing a simple command (eg, not a pipe), you could remove these inner braces.
You can't pipe outside the command as piping makes a subshell in bash
and zsh
, and the assignment to the variable in the subshell wouldn't be available to the current shell.
bash
In bash
, it would be better not to assume that file descriptor 3 is unused:
{ error=$( { { ls -ld /XXXX /bin | tr o Z ; } 1>&$tmp ; } 2>&1); } {tmp}>&1;
exec {tmp}>&- # With this syntax the FD stays open
Note that this doesn't work in zsh
.
Thanks to this answer for the general idea.
For error proofing your commands:
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
}
Inspired in Lean manufacturing:
- Make errors impossible by design
- Make steps the smallest
- Finish items one by one
- Make it obvious to anyone
참고URL : https://stackoverflow.com/questions/962255/how-to-store-standard-error-in-a-variable
'IT' 카테고리의 다른 글
전체 객체 또는 컨테이너에 객체에 대한 포인터를 저장해야합니까? (0) | 2020.06.02 |
---|---|
안드로이드가 프로세스를 죽이는 것을 시뮬레이션하는 방법 (0) | 2020.06.02 |
C # 또는 VB.NET에서 할 수없는 MSIL에서 무엇을 할 수 있습니까? (0) | 2020.06.02 |
PHP 5 : const 대 정적 (0) | 2020.06.02 |
C ++ 맵에서 insert vs emplace vs operator [] (0) | 2020.06.02 |