스크립트 출력 캡처 및 수집 중 "입력 파일이 출력 파일입니다" 오류가 발생합니까?

스크립트 출력 캡처 및 수집 중 "입력 파일이 출력 파일입니다" 오류가 발생합니까?

현재 스크립트의 출력을 업로드해야 해서 trapset -ex좋아요를 추가했습니다.

#!/bin/bash

exec &> /tmp/error.log
trap 'cat /tmp/error.log; curl http://127.0.0.1/error.php?hostname=$(hostname) -F file=@/tmp/error.log' EXIT

set -ex
wtfwtf

실행할 때 항상 이 오류가 발생하고 PHP 스크립트가 전체 파일을 수신하지 못합니다.

%> cat /tmp/error.log
1.sh: line 6: wtfwtf: command not found
cat: /tmp/error.log: input file is output file

지금까지 유일한 해결책은 error.log를 새 파일에 복사하여 업로드하는 것입니다.

#!/bin/bash

exec &> /tmp/error.log
trap 'cp /tmp/error.log 123; curl http://127.0.0.1/error.php?hostname=$(hostname) -F file=@123' EXIT

set -ex
wtfwtf

이를 수행하는 더 좋은 방법이 있습니까?

답변1

를 사용하면 exec스크립트의 모든 출력을 특정 로그 파일로 리디렉션할 수 있습니다.

트랩에서 cat모든 출력도 해당 파일로 리디렉션되므로 GNU는 cat입력 파일과 표준 출력 스트림(셸에서 상속됨)이 동일한 것임을 확인하고 작업 수행을 거부합니다.

BSD는 catGNU와 동일한 검사를 수행하지 않으며 cat스크립트가 중단되지 않으면 반복해서 반복되는 동일한 몇 줄을 포함하는 무한히 큰 로그 파일을 생성합니다.

해결 방법은 원래 표준 출력 파일 설명자를 저장하고 이전과 같이 리디렉션한 다음 트랩에 복원하는 것입니다.

#!/bin/bash

exec 3>&1                  # make fd 3 copy of original fd 1
exec >/tmp/error.log 2>&1

# in the trap, make fd 1 copy of fd 3 and close fd 3 (i.e. move fd 3 to fd 1)
trap 'exec 1>&3-; cat /tmp/error.log; curl "http://127.0.0.1/error.php?hostname=$(hostname)" -F file=@/tmp/error.log' EXIT

set -ex
wtfwtf

그러면 로그 파일로 리디렉션되기 전에 파일 설명자 1(fd 3)의 복사본이 생성됩니다. 트랩에서는 이 복사본을 다시 fd 1로 이동하고 출력합니다.

이 예의 트랩에 있는 표준 오류 스트림은 여전히 ​​로그 파일에 연결되어 있습니다. 따라서 curl진단 메시지가 생성되면 해당 메시지는 터미널(또는 원래 표준 오류 스트림이 연결된 위치)에 표시되지 않고 로그 파일에 저장됩니다.


가져가다댓글: Stéphane Chazelas고려하면:

#!/bin/sh

exit_handler () {
    # 1. Make standard output be the original standard error
    #    (by using fd 3, which is a copy of original fd 2)
    # 2. Do the same with standard error
    # 3. Close fd 3.
    exec >&3 2>&3 3>&-
    cat "$logfile"
    curl "some URL" -F "file=@$logfile"
}

logfile='/var/log/myscript.log'

# Truncate the logfile.
: >"$logfile"

# 1. Make fd 3 a copy of standard error (fd 2)
# 2. Redirect original standard output to the logfile (appending)
# 3. Redirect original standard error to the logfile (will also append)
exec 3>&2 >>"$logfile" 2>&1

# Use shell function for exit trap (for neatness)
trap exit_handler EXIT

set -ex
wtfwtf

그의 요점은 로그 파일은 어쨌든 진단 메시지에만 사용되므로 로그 파일을 원시 표준 오류 스트림으로 출력하는 것이 더 합리적이라는 것입니다.

그는 또한 누구나 쓸 수 있는 디렉토리(예: . ) 에 /tmp파일이 존재하지 않는지 확인하는 검사가 스크립트에 없기 때문이라고 지적합니다 (예: 누군가 또는 일부 악성 프로그램이 사용자 /tmp/error.log에게 심볼릭 링크를 생성했을 수 있음 /etc/passwd). ~/.bashrc해결 방법은 아래 스크립트에 대한 전용 영구 로그 파일을 /var/log대신 사용하는 것입니다(파일은 영구이지만 스크립트가 실행되면 내용이 지워집니다).

이에 대한 변형은 mktemp다음을 사용하여 고유한 파일 이름을 만드는 것입니다 $TMPDIR(그런 다음 실패 EXIT하지 않는 한 트랩에서 파일을 삭제합니다 curl. 이 경우 rm유효하므로 실행되지 않습니다).set -e

#!/bin/sh

exit_handler () {
    # 1. Make standard output be the original standard error
    #    (by using fd 3, which is a copy of original fd 2)
    # 2. Do the same with standard error
    # 3. Close fd 3.
    exec >&3 2>&3 3>&-
    cat "$logfile"
    curl "some URL" -F "file=@$logfile"
    rm -f "$logfile"
}

logfile=$( mktemp )

# 1. Make fd 3 a copy of standard error (fd 2)
# 2. Redirect original standard output to the logfile (appending)
# 3. Redirect original standard error to the logfile (will also append)
exec 3>&2 >>"$logfile" 2>&1

# Use shell function for exit trap (for neatness)
trap exit_handler EXIT

set -ex
wtfwtf

cat두 번째 예는 작동하지만 로그 파일을 복사하기 때문이 아니라 로그 파일을 사용하지 않기 때문에 작동합니다 .


작은 문제: 명령줄의 URL은 쉘이 특수 문자로 해석할 수 있는 문자(예: )를 포함하는 경향이 있으므로 최소한 큰따옴표로 묶어야 합니다 ?.

관련 정보