SOFTMOA TECHNOLOGY

Bash 파이프라인 실패가 성공으로 표시될 때 pipefail과 PIPESTATUS 확인 방법

소프트모아가 정리한 기술 기록입니다.

Contents
작성일 2026. 11. 10.

Bash 스크립트에서 작업 명령 뒤에 | tee를 붙였더니 실패한 작업이 성공으로 기록된다면, 파이프라인의 종료 코드부터 확인하세요. 기본 설정에서는 마지막 명령의 종료 코드를 쓰므로 앞 명령이 실패해도 tee가 성공하면 $?는 0입니다. set -o pipefail로 실패를 전체 결과에 반영하고, 어느 명령이 실패했는지는 PIPESTATUS를 즉시 복사해 확인합니다.

출력된 문자열과 작업 성공은 다릅니다

아래는 실제 백업이나 배포를 실행하지 않는 진단 예제입니다. 첫 명령은 partial을 출력한 뒤 의도적으로 7을 반환하고, tee는 받은 내용을 화면과 /dev/null로 보냅니다. 로그 파일을 만들거나 기존 데이터를 바꾸지 않습니다. partial이 보인다는 사실만으로 첫 명령이 끝까지 성공했다고 판단하면 안 됩니다.

예제는 Bash 전용입니다. macOS의 /bin/bash 3.2.57에서 실행했고, 동작 규칙은 GNU Bash 5.3 매뉴얼과 대조했습니다. 기본 셸이 zsh인 터미널이나 sh에 코드를 그대로 붙여 넣지 말고, 첫 코드를 demo.sh로 저장한 뒤 bash --noprofile --norc demo.sh로 실행하세요. 첫 줄에서 실제 실행 버전을 확인합니다. set +e는 실패 뒤에도 비교 결과를 출력하려고 이 진단 스크립트 안에서만 사용합니다.

#!/bin/bash
set +e
set +o pipefail
printf 'Bash=%s\n' "$BASH_VERSION"
(printf 'partial\n'; exit 7) | tee /dev/null
saved=("$?" "${PIPESTATUS[@]}")
printf 'off: total=%s producer=%s tee=%s\n' "${saved[@]}"
set -o pipefail
(printf 'partial\n'; exit 7) | tee /dev/null
saved=("$?" "${PIPESTATUS[@]}")
printf 'on: total=%s producer=%s tee=%s\n' "${saved[@]}"

두 실행 모두 partial을 출력합니다. 첫 명령의 상태는 같고, 파이프라인 전체의 판정만 달라집니다. 실제 확인한 상태 출력은 다음과 같습니다.

off: total=0 producer=7 tee=0
on: total=7 producer=7 tee=0

pipefail을 켜도 producer의 7이 0으로 복구되지는 않습니다. 숨겨졌던 실패를 total에 반영할 뿐입니다. 이 예제에서 tee의 0은 입력을 처리했다는 결과이며, 원래 작업의 완료나 산출물의 정확성을 보증하지 않습니다.

PIPESTATUS를 읽기 전에 실행한 한 줄이 증거를 바꿉니다

PIPESTATUS에는 직전에 실행한 전경 파이프라인의 각 종료 코드가 왼쪽부터 들어갑니다. 여기서는 첫 요소가 producer, 다음 요소가 tee입니다. 단일 명령을 실행한 뒤에도 값이 갱신되므로, 중간에 확인용 printf를 넣으면 이전 상태를 잃습니다.

rc=$?로 전체 종료 코드를 먼저 저장한 뒤 PIPESTATUS를 읽는 방식도 주의하세요. 대입 자체가 새 명령이어서, 위 실패 파이프라인 뒤에 실행하면 rc는 7이지만 PIPESTATUS는 0만 남습니다. 예제의 saved=("$?" "${PIPESTATUS[@]}")는 같은 대입문 안에서 전체 코드와 각 코드를 함께 복사합니다. 이후 출력 명령이 상태를 바꿔도 saved의 값은 유지됩니다.

pipefail의 전체 코드는 실패 개수도, 가장 큰 숫자도 아닙니다. 명령을 나열한 순서에서 가장 오른쪽의 0이 아닌 종료 코드를 씁니다. 별도 확인에서 세 명령이 각각 7, 3, 0을 반환하자 전체 코드는 3이었습니다. 실행이 끝난 시간순으로 마지막 실패를 고르는 규칙은 아닙니다.

실패를 기록한 뒤 호출자에게 같은 코드를 돌려주기

set -e와 pipefail을 함께 쓰면 실패 직후 스크립트가 끝나 상태 출력까지 도달하지 못할 수 있습니다. 이 경우 파이프라인을 if의 검사 자리에 두고, 각 분기의 첫 줄에서 상태를 복사합니다. 아래 코드를 handled.sh로 저장해 bash --noprofile --norc handled.sh로 실행하면 total=7, producer=7, tee=0을 출력하고 스크립트 자체도 7로 종료합니다.

#!/bin/bash
set -e
set -o pipefail
if (printf 'partial\n'; exit 7) | tee /dev/null; then
  saved=("$?" "${PIPESTATUS[@]}")
else
  saved=("$?" "${PIPESTATUS[@]}")
fi
printf 'total=%s producer=%s tee=%s\n' "${saved[@]}"
exit "${saved[0]}"

마지막 exit를 빼면 성공한 printf 때문에 스크립트의 최종 종료 코드가 0이 될 수 있습니다. 실패를 로그에 남기는 것과 예약 실행기나 CI에 실패를 전달하는 것은 별도입니다. 호출자는 출력 문구만 찾지 말고 스크립트의 종료 코드도 받도록 구성해야 합니다.

  • if 검사 안에서는 set -e의 자동 종료에 기대지 않습니다. 실제 작업을 여러 명령의 함수로 바꾼다면 함수 내부에서도 각 실패를 명시적으로 처리하세요.
  • pipefail은 현재 셸의 판정 규칙입니다. 다른 실행 셸이나 별도 스크립트에도 설정됐다고 가정하지 마세요.
  • 이 글은 전경 파이프라인을 다룹니다. 끝에 &를 붙인 백그라운드 작업은 대기와 결과 수집을 별도로 설계해야 합니다.

공식 동작은 Bash 매뉴얼의 Pipelines, PIPESTATUS 설명, set 옵션에서 확인할 수 있습니다. 소프트모아의 시스템 구축 안내는 softmoa.com에 있습니다.

소프트모아는 해당 시스템을 구축합니다. 문의하기

Share this

OS

Have a questions?

견적 및 기술문의

mobile : 010-7931-4813

Contact Form