Bash pipefail로 CI 실패 찾기와 SIGPIPE 구분하기
Bash에서 tee나 cat이 앞 단계의 실패를 가릴 때 pipefail과 PIPESTATUS로 원인을 찾고, head가 일찍 닫은 파이프의 SIGPIPE를 구분한다.
Linux CI에서 build | tee build.log가 성공으로 기록됐는데 산출물이 비어 있다면, Bash의 set -o pipefail을 켜고 실패 직후 PIPESTATUS를 확인한다. 기본 설정에서는 마지막 명령의 상태만 파이프라인의 결과가 된다. 반대로 head가 먼저 읽기를 끝낸 파이프에서는 앞 단계가 SIGPIPE로 종료될 수 있다. 두 경우를 로컬에서 재현하고, 전체 출력을 검증해야 할 때 쓸 수 있는 패턴을 살펴본다.
오른쪽 구간은 활성 상태지만 왼쪽 구간에 주황색 단절이 표시된 두 단계 파이프
마지막 명령이 앞 단계의 실패를 가리는 이유
Bash는 기본적으로 파이프라인의 상태를 마지막 명령의 종료 상태로 정한다. 빌드가 출력 일부를 남기고 7로 끝나도, 그 출력을 읽어 저장한 tee가 0으로 끝나면 CI가 전체를 성공으로 기록할 수 있다. pipefail을 켜면 실패한 명령 중 파이프라인에서 가장 오른쪽에 있는 명령의 상태가 반환된다. 실패한 명령이 없으면 0이다. 여러 단계가 실패한 경우에는 이 값 하나만으로 다른 실패를 확인할 수 없다. GNU Bash 파이프라인 문서
PIPESTATUS는 가장 최근에 실행한 포그라운드 파이프라인의 단계별 종료 상태를 왼쪽부터 담는다. 중간에 echo를 실행하거나 rc=$?를 별도 명령으로 먼저 실행하면 원래 배열이 바뀐다. 예제처럼 한 번의 배열 할당에서 $?와 PIPESTATUS를 동시에 저장한다. 첫 원소가 파이프라인 전체 상태이고, 나머지가 각 단계의 상태다. GNU Bash 변수 문서
단계별 종료 상태 재현하기
아래 코드를 bash-pipeline-status.sh에 저장하고 bash bash-pipeline-status.sh로 실행한다. 예제는 실패 뒤에도 다음 실험을 실행하도록 errexit을 끈다. build는 일부 출력을 남긴 뒤 종료 상태 7을 반환하는 모의 빌드다.
실행 결과와 SIGPIPE 판별
2026년 11월 9일 Linux x86-64에서 GNU Bash 5.2.37과 GNU Coreutils 9.7로 실행했다. 스크립트의 여섯 상태 검사가 모두 통과했다. 출력에서 단독으로 표시된 1은 마지막 실험이 출력한 첫 번째 줄이다.
default와 pipefail에서 단계별 상태는 모두 (7 0)이다. 달라진 것은 전체 상태다. multiple은 두 단계의 실패 중 가장 오른쪽인 9를 반환한다.
early-close에서는 head가 한 줄을 읽고 종료한다. 파이프의 읽기 쪽을 연 프로세스가 하나도 남지 않으면 그 파이프에 쓰는 프로세스에 SIGPIPE가 전달된다. 이 환경에서 Bash는 신호 13에 의한 종료를 128 + 13 = 141로 보고했다. ignored-signal은 같은 상황에서 신호를 무시하도록 설정한 사례다. 이때 쓰기 호출이 EPIPE로 실패하고 GNU yes가 1을 반환했다. 두 사례를 재현하기 위해 GNU env로 신호 처리를 명시했다. Linux pipe(7) · GNU Bash 종료 상태 · GNU Coreutils env
CI에서 실패로 처리할 범위 정하기
빌드 로그를 남기면서 빌드 실패를 잡으려면 Bash를 명시하고 pipefail을 켠 뒤 파이프라인을 if로 검사한다. 실패 분기에서 로그를 추가로 출력하기 전에 상태를 캡처하고, CI 단계가 실패를 전달하도록 종료 상태를 반환한다. if 조건에 호출한 함수나 복합 명령 내부에서는 errexit의 예외가 적용될 수 있으므로, 중요한 내부 명령의 실패도 함수에서 명시적으로 반환한다. 예제는 Bash에서 실행했으며 CI 서비스별 기본 셸과 실행 옵션은 검증 범위에서 제외했다. GNU Bash set 문서
유한한 출력을 끝까지 읽되 첫 줄만 보여주려면 awk 'NR == 1 { print }'처럼 exit 없이 입력을 끝까지 소비할 수 있다. 이 방식은 생산자가 끝날 때까지 기다리므로 무한 스트림에는 맞지 않는다. 조기 종료가 의도된 경우에는 해당 파이프라인에서 어떤 단계가 왜 실패했는지 확인한 뒤 처리한다. 상태 141을 일괄 무시하거나 전체 뒤에 || true를 붙이면 실제 빌드·로그 쓰기 오류도 가릴 수 있다.
English version ·
Debug Bash Pipeline Failures in CI with pipefail and PIPESTATUS