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


105

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

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

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

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

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


: 비슷한 질문 : 다른 독자들에 대한 stackoverflow.com/questions/692000/...
pevik

1
@JasonSydes를 제외한 모든 사람 (나를 포함하여!)이 탈선하여 다른 질문에 답한 것에 짜증이납니다. 그리고 Jason의 대답은 제가 언급했듯이 신뢰할 수 없습니다. 나는 당신이 물어 본 (그리고 당신의 EDIT에서 강조한) 질문에 대한 진정한 대답을보고 싶습니다.
Don Hatch

아, 잠시만 요. @PaulTromblin의 수락 된 답변이 대답합니다. 나는 그것에 대해 충분히 읽지 않았습니다.
Don Hatch

답변:


169

"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

4
쉘 스크립트에서 명령을 괄호로 묶을 수 있다는 것을 몰랐습니다. 흥미 롭군.
Jamie

1
브래킷 바로 가기도 감사합니다! 어떤 이유로 2>&1 | tee -a filenamestderr를 내 스크립트의 파일에 저장하지 않았지만 명령을 복사하여 터미널에 붙여 넣으면 제대로 작동했습니다! 그래도 브래킷 트릭은 잘 작동합니다.
Ed Brannin

8
tee가 모든 것을 stdout에 인쇄하므로 stdout과 stderr의 구별이 사라집니다.
Flimm 2015 년

2
참고 : 'script'명령은 대부분의 배포판에서 사용할 수 있습니다 (util-linux 패키지의 일부)
SamWN

2
@Flimm, stdout과 stderr의 구별을 유지하는 방법 (다른 방법)이 있습니까?
Gabriel

20

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을 포착하지 못했습니다. 여기 에서 메모로 수정 되었습니다 .


3
tee가 모든 것을 stdout에 인쇄하므로 stdout과 stderr의 구별이 사라집니다.
Flimm 2015 년

@Flimm stderr은 다시 stderr로 리디렉션 될 수있는 다른 tee 프로세스로 리디렉션 될 수 있습니다.
jarno

@Flimm, 여기에 jarno의 제안을 썼습니다. stackoverflow.com/a/53051506/1054322
MatrixManAtYrService

1
이 솔루션은 지금까지 제안 된 대부분의 다른 솔루션과 마찬가지로 경쟁에 취약합니다. 즉, 현재 스크립트가 완료되고 사용자의 프롬프트 또는 일부 상위 수준의 호출 스크립트로 돌아 오면 백그라운드에서 실행중인 티가 계속 실행되고 화면에 마지막 몇 줄을 방출 할 수 있습니다. 로그 파일 늦음 (즉, 프롬프트 후 화면 및 로그 파일이 완료 될 것으로 예상 된 후 로그 파일).
Don Hatch

1
그러나 이것은 실제로 질문을 해결하는 유일한 답변입니다!
Don Hatch

9

패턴

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에만 표시됩니다.


이것은 정말 유용하고 내가 원하는 것 같습니다. 그래도 Windows 배치 스크립트에서 대괄호 사용 (첫 번째 줄 참조)을 복제하는 방법을 모르겠습니다. ( tee문제의 시스템에서 사용할 수 있습니다.) "다른 프로세스에서 파일을 사용하고 있기 때문에 프로세스가 파일에 액세스 할 수 없습니다."라는 오류가 표시됩니다.
Agi Hammerthief

이 솔루션은 지금까지 제안 된 대부분의 다른 솔루션과 마찬가지로 경쟁에 취약합니다. 즉, 현재 스크립트가 완료되고 사용자의 프롬프트 또는 일부 상위 수준의 호출 스크립트로 돌아 오면 백그라운드에서 실행중인 티가 계속 실행되고 화면에 마지막 몇 줄을 방출 할 수 있습니다. 로그 파일 늦음 (즉, 프롬프트 후 화면 및 로그 파일이 완료 될 것으로 예상 된 후 로그 파일).
Don Hatch

2
@DonHatch이 문제를 수정하는 솔루션을 제안 할 수 있습니까?
pylipp

나는 또한 인종을 분명하게 만드는 테스트 케이스에 관심이 있습니다. 의심스러워서가 아니라 그런 일이 일어나지 않았기 때문에 피하려고 시도하는 것은 어렵습니다.
MatrixManAtYrService

@pylipp 해결책이 없습니다. 나는 하나에 매우 관심 이 있습니다 .
Don Hatch

4

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

2
tee가 모든 것을 stdout에 인쇄하므로 stdout과 stderr의 구별이 사라집니다.
Flimm 2015 년

4

편집 : 탈선하여 요청한 질문과 다른 질문에 답하게 된 것을 봅니다. 실제 질문에 대한 답변은 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을의 암시 티잉하는 것입니다,. 내가 시행 착오에 의한 것을 발견)


나는 bash에서 이것을 가지고 놀았고 잘 작동합니다. (나와 같이) 호환성을 가정하는 습관을 가진 zsh 사용자에 대한 경고 일뿐입니다
MatrixManAtYrService

@MatrixManAtYrService 나는 zsh 상황에 대한 핸들을 얻었으며 zsh에 훨씬 더 깔끔한 솔루션이 있음이 밝혀졌습니다. 내 편집 "zsh에 대한 각주"를 참조하십시오.
Don Hatch

솔루션을 자세히 설명해 주셔서 감사합니다. my_function중첩 된 stdout / stderr 필터링에서 함수 ( )를 사용할 때 반환 코드를 검색하는 방법도 알고 있습니까? 내가 { { my_function || touch failed;} 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3- | stdout_filter했지만 실패의 표시기로 파일을 만드는 것이 이상하게 느껴집니다 ...
pylipp

@pylipp 나는 손을 대지 않는다. 별도의 질문으로 질문 할 수 있습니다 (아마도 간단한 파이프 라인 사용).
Don Hatch

2

사용 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
~:

1

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

 program 2>&1 | tee > logfile

1

"RunScript.sh"라는 스크립트를 만들었습니다. 이 스크립트의 내용은 다음과 같습니다.

${APP_HOME}/${1}.sh ${2} ${3} ${4} ${5} ${6} 2>&1 | tee -a ${APP_HOME}/${1}.log

나는 이것을 다음과 같이 부른다.

./RunScript.sh ScriptToRun Param1 Param2 Param3 ...

이것은 작동하지만 외부 스크립트를 통해 실행되는 응용 프로그램의 스크립트가 필요합니다. 약간 어색합니다.


9
$ 1 $ 2 $ 3 ...으로 공백을 포함하는 인수 그룹을 잃게됩니다. (따옴표 포함) : "$ @"
NVRAM

1

1 년 후, 여기에 무엇이든 로깅하기위한 오래된 bash 스크립트가 있습니다. 예를 들어,
teelog make ...생성 된 로그 이름에 로그를 기록합니다 (중첩 된 makes 를 기록하는 방법도 참조 ).

#!/bin/bash
me=teelog
Version="2008-10-9 oct denis-bz"

Help() {
cat <<!

    $me anycommand args ...

logs the output of "anycommand ..." as well as displaying it on the screen,
by running
    anycommand args ... 2>&1 | tee `day`-command-args.log

That is, stdout and stderr go to both the screen, and to a log file.
(The Unix "tee" command is named after "T" pipe fittings, 1 in -> 2 out;
see http://en.wikipedia.org/wiki/Tee_(command) ).

The default log file name is made up from "command" and all the "args":
    $me cmd -opt dir/file  logs to `day`-cmd--opt-file.log .
To log to xx.log instead, either export log=xx.log or
    $me log=xx.log cmd ...
If "logdir" is set, logs are put in that directory, which must exist.
An old xx.log is moved to /tmp/\$USER-xx.log .

The log file has a header like
    # from: command args ...
    # run: date pwd etc.
to show what was run; see "From" in this file.

Called as "Log" (ln -s $me Log), Log anycommand ... logs to a file:
    command args ... > `day`-command-args.log
and tees stderr to both the log file and the terminal -- bash only.

Some commands that prompt for input from the console, such as a password,
don't prompt if they "| tee"; you can only type ahead, carefully.

To log all "make" s, including nested ones like
    cd dir1; \$(MAKE)
    cd dir2; \$(MAKE)
    ...
export MAKE="$me make"

!
  # See also: output logging in screen(1).
    exit 1
}


#-------------------------------------------------------------------------------
# bzutil.sh  denisbz may2008 --

day() {  # 30mar, 3mar
    /bin/date +%e%h  |  tr '[A-Z]' '[a-z]'  |  tr -d ' '
}

edate() {  # 19 May 2008 15:56
    echo `/bin/date "+%e %h %Y %H:%M"`
}

From() {  # header  # from: $*  # run: date pwd ...
    case `uname` in Darwin )
        mac=" mac `sw_vers -productVersion`"
    esac
    cut -c -200 <<!
${comment-#} from: $@
${comment-#} run: `edate`  in $PWD `uname -n` $mac `arch` 

!
    # mac $PWD is pwd -L not -P real
}

    # log name: day-args*.log, change this if you like --
logfilename() {
    log=`day`
    [[ $1 == "sudo" ]]  &&  shift
    for arg
    do
        log="$log-${arg##*/}"  # basename
        (( ${#log} >= 100 ))  &&  break  # max len 100
    done
            # no blanks etc in logfilename please, tr them to "-"
    echo $logdir/` echo "$log".log  |  tr -C '.:+=[:alnum:]_\n' - `
}

#-------------------------------------------------------------------------------
case "$1" in
-v* | --v* )
    echo "$0 version: $Version"
    exit 1 ;;
"" | -* )
    Help
esac

    # scan log= etc --
while [[ $1 == [a-zA-Z_]*=* ]]; do
    export "$1"
    shift
done

: ${logdir=.}
[[ -w $logdir ]] || {
    echo >&2 "error: $me: can't write in logdir $logdir"
    exit 1
    }
: ${log=` logfilename "$@" `}
[[ -f $log ]]  &&
    /bin/mv "$log" "/tmp/$USER-${log##*/}"


case ${0##*/} in  # basename
log | Log )  # both to log, stderr to caller's stderr too --
{
    From "$@"
    "$@"
} > $log  2> >(tee /dev/stderr)  # bash only
    # see http://wooledge.org:8000/BashFAQ 47, stderr to a pipe
;;

* )
#-------------------------------------------------------------------------------
{
    From "$@"  # header: from ... date pwd etc.

    "$@"  2>&1  # run the cmd with stderr and stdout both to the log

} | tee $log
    # mac tee buffers stdout ?

esac

댓글을 달기 엔 너무 늦었지만이 대본에 감사 드려야 했어요. 매우 유용하고 잘 문서화되었습니다!
stephenmm

감사합니다 @stephenmm; 그것은 없다 결코 "유용"또는 "개선 될 수있다"말을 너무 늦게.
denis
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.