[bash] bash 스크립트 자체에서 stdout COPY를 로그 파일로 리디렉션

stdout 을 파일 로 리디렉션 하는 방법을 알고 있습니다.

exec > foo.log
echo test

이것은 ‘test’를 foo.log 파일에 넣습니다.

이제 출력을 로그 파일로 리디렉션하고 stdout에 유지하려고합니다.

즉, 스크립트 외부에서 사소하게 수행 할 수 있습니다.

script | tee foo.log

하지만 스크립트 자체에서 선언하고 싶습니다.

나는 시도했다

exec | tee foo.log

그러나 작동하지 않았습니다.



답변

#!/usr/bin/env bash

# Redirect stdout ( > ) into a named pipe ( >() ) running "tee"
exec > >(tee -i logfile.txt)

# Without this, only stdout would be captured - i.e. your
# log file would not contain any error messages.
# SEE (and upvote) the answer by Adam Spiers, which keeps STDERR
# as a separate stream - I did not want to steal from him by simply
# adding his answer to mine.
exec 2>&1

echo "foo"
echo "bar" >&2

이것은 bash아닙니다 sh. 로 스크립트를 호출하면 sh myscript.sh의 행을 따라 오류가 발생합니다 syntax error near unexpected token '>'.

신호 트랩으로 작업하는 tee -i경우 신호가 발생하는 경우 출력 중단을 피하기 위해이 옵션을 사용할 수 있습니다 . (의견에 대해 JamesThomasMoon1979에게 감사합니다.)


파이프 또는 터미널에 ls쓰는지 여부에 따라 출력을 변경하는 도구 ( 예 : 색상 및 열화 된 출력 사용)는 위의 구성을 파이프로 출력하는 것을 의미합니다.

색상 화 / 컬럼 화를 시행하는 옵션이 있습니다 (예 🙂 ls -C --color=always. 이로 인해 색상 코드가 로그 파일에도 기록되어 읽기 어려워 집니다.


답변

허용 된 답변은 STDERR을 별도의 파일 설명 자로 유지하지 않습니다. 그것의 의미는

./script.sh >/dev/null

bar터미널에 출력되지 않고 로그 파일에만 출력 됩니다.

./script.sh 2>/dev/null

터미널 foobar터미널 모두에 출력됩니다 . 분명히 그것은 일반 사용자가 기대하는 행동이 아닙니다. 동일한 로그 파일에 추가되는 두 개의 별도 티 프로세스를 사용하여이 문제를 해결할 수 있습니다.

#!/bin/bash

# See (and upvote) the comment by JamesThomasMoon1979 
# explaining the use of the -i option to tee.
exec >  >(tee -ia foo.log)
exec 2> >(tee -ia foo.log >&2)

echo "foo"
echo "bar" >&2

(위의 내용은 처음에는 로그 파일을 자르지 않습니다.이 동작을 원하면 추가해야합니다.

>foo.log

스크립트 상단에.)

의 POSIX.1-2008 사양tee(1) 이 출력 버퍼링이 필요하다, 즉 심지어 라인 버퍼링 그래서 STDOUT 및 STDERR가 동일한 행에 끝낼 수있다이 경우에 foo.log; 그러나 이는 터미널에서도 발생할 수 있으므로 로그 파일은 터미널의 정확한 미러가 아닌 경우 터미널에서 볼 있는 내용을 충실히 반영 합니다. STDOUT 행을 STDERR 행과 완전히 분리하려면 각 행에 날짜 소인 접 두부가있는 두 개의 로그 파일을 사용하여 나중에 시간순으로 다시 어셈블 할 수 있습니다.


답변

busybox, macOS bash 및 비 -bash 쉘을위한 솔루션

수락 된 답변은 bash에 가장 적합한 선택입니다. bash에 액세스하지 않고 Busybox 환경에서 작업하고 있으며 exec > >(tee log.txt)구문을 이해하지 못합니다 . 또한하지 않습니다exec >$PIPE 명명 된 파이프와 동일한 이름을 가진 일반 파일을 만들려고 시도하여 제대로 작동 실패하고 중단됩니다.

바라건대 이것은 bash가없는 다른 사람에게 유용 할 것입니다.

또한 명명 된 파이프를 사용하는 사람 rm $PIPE은 VFS에서 파이프를 연결 해제하기 때문에 안전 하지만 파이프를 사용하는 프로세스는 완료 될 때까지 참조 카운트를 유지합니다.

$ *의 사용이 반드시 안전한 것은 아닙니다.

#!/bin/sh

if [ "$SELF_LOGGING" != "1" ]
then
    # The parent process will enter this branch and set up logging

    # Create a named piped for logging the child's output
    PIPE=tmp.fifo
    mkfifo $PIPE

    # Launch the child process with stdout redirected to the named pipe
    SELF_LOGGING=1 sh $0 $* >$PIPE &

    # Save PID of child process
    PID=$!

    # Launch tee in a separate process
    tee logfile <$PIPE &

    # Unlink $PIPE because the parent process no longer needs it
    rm $PIPE

    # Wait for child process, which is running the rest of this script
    wait $PID

    # Return the error code from the child process
    exit $?
fi

# The rest of the script goes here


답변

스크립트 파일 안에 다음과 같이 모든 명령을 괄호 안에 넣으십시오.

(
echo start
ls -l
echo end
) | tee foo.log


답변

bash 스크립트 로그를 syslog에 만드는 쉬운 방법. 스크립트 출력은 /var/log/syslogstderr를 통해 또는 stderr을 통해 사용 가능합니다 . syslog는 타임 스탬프를 포함한 유용한 메타 데이터를 추가합니다.

이 줄을 맨 위에 추가하십시오.

exec &> >(logger -t myscript -s)

또는 별도의 파일로 로그를 보내십시오.

exec &> >(ts |tee -a /tmp/myscript.output >&2 )

이 필요 moreutils합니다 (위해 ts타임 스탬프를 추가 명령).


답변

수락 된 답변을 사용하면 스크립트가 나머지 부분을 백그라운드에서 실행하면서 남겨두고 ( ‘exec>> (tee …)’직후) 예외적으로 일찍 반환했습니다. 해당 솔루션을 내 방식대로 작동시킬 수 없으므로 문제에 대한 다른 솔루션 / 해결 방법을 찾았습니다.

# Logging setup
logfile=mylogfile
mkfifo ${logfile}.pipe
tee < ${logfile}.pipe $logfile &
exec &> ${logfile}.pipe
rm ${logfile}.pipe

# Rest of my script

이렇게하면 스크립트의 출력이 프로세스에서 파이프를 통해 ‘tee’의 하위 백그라운드 프로세스로 이동하여 모든 것을 디스크 및 스크립트의 원래 stdout에 기록합니다.

‘exec &>’는 stdout과 stderr을 모두 리디렉션하므로 원하는 경우 별도로 리디렉션하거나 stdout을 원하면 ‘exec>’로 변경할 수 있습니다.

스크립트가 시작될 때 파이프가 파일 시스템에서 제거 되더라도 프로세스가 완료 될 때까지 계속 작동합니다. rm 행 다음에 파일 이름을 사용하여 참조 할 수 없습니다.


답변

Bash 4에는 coproc명령에 대한 명명 된 파이프를 설정하고이를 통해 통신 할 수있는 명령이 있습니다.