[bash] STDOUT과 STDERR 모두 터미널과 로그 파일로 이동하려면 어떻게해야합니까?

비 기술적 인 사용자가 대화식으로 실행할 스크립트가 있습니다. 스크립트는 사용자가 스크립트가 정상적으로 실행되고 있는지 확인할 수 있도록 상태 업데이트를 STDOUT에 기록합니다.

STDOUT과 STDERR 모두 터미널로 리디렉션되기를 원합니다 (사용자가 스크립트가 작동하고 문제가 있는지 확인할 수 있도록). 또한 두 스트림 모두 로그 파일로 리디렉션되기를 원합니다.

인터넷에서 많은 솔루션을 보았습니다. 일부는 작동하지 않고 다른 일부는 끔찍하게 복잡합니다. 나는 실행 가능한 솔루션을 개발했지만 (답으로 입력 할 것입니다), 어리 석습니다.

완벽한 솔루션은 터미널과 로그 파일 모두에 두 스트림을 모두 보내는 스크립트의 시작 부분에 통합 될 수있는 코드 한 줄입니다.

편집 : STDERR을 STDOUT으로 리디렉션하고 결과를 티에 파이프 작업하지만 출력을 리디렉션하고 파이프하는 것을 기억하는 사용자에 따라 다릅니다. 로깅이 안전하고 자동으로 이루어지기를 원합니다 (그래서 스크립트 자체에 솔루션을 포함 할 수 있기를 원합니다.)



답변

“tee”를 사용하여 파일과 화면으로 리디렉션합니다. 사용하는 셸에 따라 먼저 다음을 사용하여 stderr을 stdout으로 리디렉션해야합니다.

./a.out 2>&1 | tee output

또는

./a.out |& tee output

csh에는 화면으로 이동하는 모든 것을 파일로 캡처하는 “script”라는 내장 명령이 있습니다. “script”를 입력하여 시작하고 캡처하려는 작업을 수행 한 다음 control-D를 눌러 스크립트 파일을 닫습니다. sh / bash / ksh에 해당하는 항목을 모르겠습니다.

또한 수정할 수있는 자체 sh 스크립트라고 표시 했으므로 전체 스크립트를 다음과 같이 중괄호 또는 대괄호로 묶어 내부적으로 리디렉션을 수행 할 수 있습니다.

  #!/bin/sh
  {
    ... whatever you had in your script before
  } 2>&1 | tee output.file


답변

50 년이 지난 후 …

나는 이것이 OP가 추구하는 “완벽한 해결책”이라고 믿는다.

다음은 Bash 스크립트 상단에 추가 할 수있는 한 줄입니다.

exec > >(tee -a $HOME/logfile) 2>&1

다음은 그 사용을 보여주는 작은 스크립트입니다.

#!/usr/bin/env bash

exec > >(tee -a $HOME/logfile) 2>&1

# Test redirection of STDOUT
echo test_stdout

# Test redirection of STDERR
ls test_stderr___this_file_does_not_exist

(참고 : 이것은 단지 강타와 함께 작동 그것은 것이다. 하지 / bin / sh 작동 .)

여기 에서 적응 ; 원본은 내가 알 수 있듯이 로그 파일에서 STDERR을 포착하지 못했습니다. 여기 에서 메모로 수정 되었습니다 .


답변

패턴

the_cmd 1> >(tee stdout.txt ) 2> >(tee stderr.txt >&2 )

이 표준 에러 별도로 표준 출력 모두를 리디렉션, 그리고 전송 분리 (터미널이 될 수 있음) 호출자에게 표준 출력 및 표준 오류의 사본을.

  • zsh에서는 tees가 완료 될 때까지 다음 문으로 진행되지 않습니다 .

  • 배쉬에서는 출력의 마지막 몇 줄이 나타나지 않을 수 있습니다 다음에 오는 어떤 문.

두 경우 모두 올바른 비트가 올바른 위치로 이동합니다.


설명

다음은 스크립트 (./example에 저장 됨)입니다.

#! /usr/bin/env bash
the_cmd()
{
    echo out;
    1>&2 echo err;
}

the_cmd 1> >(tee stdout.txt ) 2> >(tee stderr.txt >&2 )

세션은 다음과 같습니다.

$ foo=$(./example)
    err

$ echo $foo
    out

$ cat stdout.txt
    out

$ cat stderr.txt
    err

작동 방식은 다음과 같습니다.

  1. tee프로세스가 모두 시작되고 해당 stdin이 파일 설명자에 할당됩니다. 프로세스 대체로 묶여 있기 때문에 해당 파일 설명자에 대한 경로는 호출 명령에서 대체되므로 이제 다음과 같이 보입니다.

the_cmd 1> /proc/self/fd/13 2> /proc/self/fd/14

  1. the_cmd 실행되고 stdout을 첫 번째 파일 설명자에 쓰고 stderr을 두 번째 파일 설명자에 씁니다.

  2. bash의 경우 the_cmd완료되면 다음 명령문이 즉시 발생합니다 (터미널이 호출자 인 경우 프롬프트가 표시됨).

  3. zsh의 경우 the_cmd완료되면 셸은 tee계속 진행하기 전에 두 프로세스가 모두 완료 될 때까지 기다립니다 . 여기 에 대한 자세한 내용 .

  4. 의 stdout tee에서 읽는 첫 번째 프로세스 the_cmd는 해당 stdout의 복사본을 호출자에게 다시 씁니다 tee. 출력은 리디렉션되지 않으므로 변경되지 않은 상태로 호출자에게 다시 보냅니다.

  5. 두 번째 tee프로세스는 stdout호출자에게 리디렉션됩니다 stderr(stdin이 the_cmd‘s stderr 에서 읽고 있기 때문에 좋습니다 ). 따라서 stdout에 쓸 때 해당 비트는 호출자의 stderr로 이동합니다.

이렇게하면 파일과 명령 출력 모두에서 stderr을 stdout과 별도로 유지합니다.

첫 번째 티가 오류를 작성하면 stderr 파일과 명령의 stderr에 모두 표시되고 두 번째 티가 오류를 작성하면 터미널의 stderr에만 표시됩니다.


답변

stderr을 stdout으로 리디렉션하려면 명령에 다음을 추가하십시오. 2>&1
터미널로 출력하고 파일에 로그인하려면 다음을 사용해야합니다.tee

둘 다 함께 다음과 같이 보일 것입니다.

 mycommand 2>&1 | tee mylogfile.log

편집 : 스크립트에 포함하려면 똑같이 할 것입니다. 그래서 당신의 스크립트

#!/bin/sh
whatever1
whatever2
...
whatever3

결국

#!/bin/sh
( whatever1
whatever2
...
whatever3 ) 2>&1 | tee mylogfile.log


답변

편집 : 탈선하여 요청한 질문과 다른 질문에 답하게 된 것을 봅니다. 실제 질문에 대한 답변은 Paul Tomblin의 답변 하단에 있습니다. (어떤 이유로 stdout과 stderr를 별도로 리디렉션하도록 해당 솔루션을 향상 시키려면 여기에서 설명하는 기술을 사용할 수 있습니다.)


나는 stdout과 stderr의 구별을 유지하는 답변을 원했습니다. 불행히도 지금까지 그 구별을 유지하는 모든 답변은 인종에 취약합니다. 제가 코멘트에서 지적했듯이 프로그램이 불완전한 입력을 보는 위험이 있습니다.

나는 마침내 구별을 유지하고 인종에 취약하지 않고 끔찍하게 어리석지 않은 답을 찾았다 고 생각합니다.

첫 번째 빌딩 블록 : stdout과 stderr를 바꾸려면 :

my_command 3>&1 1>&2 2>&3-

두 번째 빌딩 블록 : stderr 만 필터링 (예 : 티)하려면 stdout & stderr를 교체하고 필터링 한 다음 다시 교체하여이를 수행 할 수 있습니다.

{ my_command 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3-

이제 나머지는 간단합니다. 처음에 stdout 필터를 추가 할 수 있습니다.

{ { my_command | stdout_filter;} 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3-

또는 끝에 :

{ my_command 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3- | stdout_filter

위의 두 명령이 모두 작동한다는 것을 확신하기 위해 다음을 사용했습니다.

alias my_command='{ echo "to stdout"; echo "to stderr" >&2;}'
alias stdout_filter='{ sleep 1; sed -u "s/^/teed stdout: /" | tee stdout.txt;}'
alias stderr_filter='{ sleep 2; sed -u "s/^/teed stderr: /" | tee stderr.txt;}'

출력은 다음과 같습니다.

...(1 second pause)...
teed stdout: to stdout
...(another 1 second pause)...
teed stderr: to stderr

내 프롬프트는 teed stderr: to stderr예상대로 ” ” 바로 뒤에 다시 표시 됩니다.

zsh에 대한 각주 :

위의 솔루션은 bash에서 작동하지만 (확실하지 않은 다른 셸도있을 수 있음) zsh에서는 작동하지 않습니다. zsh에서 실패하는 이유는 두 가지입니다.

  1. 구문 2>&3-은 zsh에서 이해되지 않습니다. 다시 작성해야합니다.2>&3 3>&-
  2. zsh에서 (다른 셸과 달리) 이미 열려있는 파일 설명자를 리디렉션하면 경우에 따라 (결정 방법을 완전히 이해하지 못함) 대신 내장 티와 같은 동작을 수행합니다. 이를 방지하려면 리디렉션하기 전에 각 fd를 닫아야합니다.

예를 들어 두 번째 솔루션은 zsh에 대해 {my_command 3>&1 1>&- 1>&2 2>&- 2>&3 3>&- | stderr_filter;} 3>&1 1>&- 1>&2 2>&- 2>&3 3>&- | stdout_filter(bash에서도 작동하지만 매우 장황합니다) 로 다시 작성해야합니다 .

반면에 zsh의 신비한 내장 암시 적 티잉을 활용하여 티를 전혀 실행하지 않는 zsh에 대한 훨씬 더 짧은 솔루션을 얻을 수 있습니다.

my_command >&1 >stdout.txt 2>&2 2>stderr.txt

(필자는 문서에서 짐작하지 않았을 나는 것을 발견 >&1하고 2>&2트리거 zsh을의 암시 티잉하는 것입니다,. 내가 시행 착오에 의한 것을 발견)


답변

사용 script스크립트 명령을 (man 1 스크립트).

script ()를 설정하고 exit를 호출하는 래퍼 셸 스크립트 (2 줄)를 만듭니다.

1 부 : wrap.sh

#!/bin/sh
script -c './realscript.sh'
exit

2 부 : realscript.sh

#!/bin/sh
echo 'Output'

결과:

~: sh wrap.sh
Script started, file is typescript
Output
Script done, file is typescript
~: cat typescript
Script started on fr. 12. des. 2008 kl. 18.07 +0100
Output

Script done on fr. 12. des. 2008 kl. 18.07 +0100
~:


답변

tee 프로그램을 사용하고 stderr을 stdout으로 복제하십시오.

 program 2>&1 | tee > logfile