
컨텍스트: 파일을 복사하는 Bash 스크립트가 있습니다.
function log () {
read IN
if [ "$IN" == "" ]; then
:
else
echo "$datetime"$'\t'"$IN" | tee -a logfile
fi
}
function copy () {
command cp -L --parents $@
}
...
copy -R /etc . 2>&1 | log
...
문제: 이 명령 cp -L --parents -R /etc 2>&1
을 수동으로 실행하면 약 10번의 실패(Symlink 깨짐, 예상)가 발생하고 전체 /etc가 복사되었습니다.
그러나 스크립트가 실행되면 1개의 실패만 보고되고, /etc는 1개의 실패가 발생한 지점에만 복사됩니다.
문제를 해결하기 위해 내가 한 일은 2>&1
스크립트에서 제거하는 것뿐이었고 복사는 예상대로 진행되었습니다.
질문: 내 log
기능이 문제를 일으키는 것인가요? 아니면 스크립트 작성 방식에 있는 구문상의 문제(스크립트를 깨뜨리지는 않더라도)인가요?
답변1
귀하의 log
기능이 범인입니다. 수행하는 작업은 다음과 같습니다. 한 줄을 읽고, 줄이 비어 있지 않으면 타임스탬프를 인쇄한 다음 줄 내용을 인쇄합니다. 그게 전부입니다. 한 줄을 처리하면 반환됩니다.
cp
첫 번째 오류 메시지를 내보내면 함수 log
는 이를 읽고 처리합니다. 그런 다음 함수 log
가 반환되므로 파이프 오른쪽에 있는 프로세스가 종료되고 이로 인해 파이프의 읽기 끝이 닫힙니다. 두 번째 오류 메시지를 내보낼 때 cp
닫힌 파이프에 쓰려고 시도하며 이로 인해 중단됩니다.SIGPIPE 신호. 표준 오류는 라인 버퍼링되므로(기본적으로 cp
이를 변경하려고 시도하지 않음) 버퍼링이 작동하지 않습니다.
모든 입력 줄을 처리하려면 read
루프가 필요합니다.
log () {
while IFS= read -r IN; do
echo "$datetime"$'\t'"$IN"
done | tee -a logfile >&2
}
read
나는 또한IFS= read -r
실제로 한 줄을 읽으려면. 무의미한 빈 줄에 대한 특수 처리를 제거했습니다(입력에 빈 줄이 없습니다). 입력이 비어 있는 경우(입력 행이 0개)인 경우를 처리하기 위해 넣었다고 가정하지만 이를 처리하는 올바른 방법은 명령의 반환 상태를 확인하는 것입니다 read
. 또한 log
오류 메시지를 처리하는 데 사용되므로 표준 오류로 인쇄하도록 수정했습니다 .
보다명령의 각 출력 줄에 타임스탬프 추가다른 방법으로는 이 작업을 수행할 수 없습니다.
파이프의 왼쪽에 명령을 입력하면 큰 단점이 있다는 점에 유의하세요.종료 상태가 무시됩니다.. 따라서 cp
실패하면 스크립트가 문제없이 계속됩니다. 오류는 어딘가에 기록되지만 후속 명령은 정상적으로 실행되며 로그를 읽어야 한다는 사실을 알려주는 내용은 없습니다. Bash, ksh 또는 zsh에서는 다음을 설정할 수 있습니다.pipefail
옵션또한 set -e
파이프라인의 왼쪽에서도 명령이 실패하는 즉시 스크립트가 오류 상태로 종료되도록 합니다.
set -o errexit -o pipefail
copy … |& log
또는 다음을 사용하십시오.프로세스 대체오류 출력을 다른 프로세스를 통해 파이프하는 파이프 대신. 프로세스 대체에는 파이프와 약간 다른 주의사항이 있습니다. 오류는 log
효과적으로 무시되며 명령은 log
완료되기 전에 반환될 수 있습니다(bash에서는 log
백그라운드에서 실행됨).
set -e
copy … 2> >(log)
¹ 거의 IFS= read -r IN
더 이상 읽을 필요가 없으며 줄을 엉망으로 만들 수도 없습니다.