iBetter Books
수정

set -e, set -u, set -o pipefail

방어적인 스크립트를 작성하는 가장 빠른 방법이 있습니다. 스크립트 맨 첫 줄에 이것을 넣는 것입니다.

set -euo pipefail

세 글자처럼 보이지만 실제로는 세 가지 옵션의 조합입니다. 각각이 무엇을 하고, 왜 필요한지, 그리고 어떤 함정이 있는지 하나씩 살펴봅니다.

set -e — 에러 발생 시 즉시 종료

기본 상태의 Bash는 관대합니다. 명령어가 실패해도 다음 줄을 실행합니다. set -e를 켜면 명령어가 0이 아닌 종료 코드를 반환하는 순간 스크립트 전체가 종료됩니다.

# 파일: without_set_e.sh#!/usr/bin/env bashcp /없는파일 /tmp/   # 실패echo "이 줄도 실행됩니다."   # set -e 없이는 실행됨echo "끝"
# 파일: with_set_e.sh#!/usr/bin/env bashset -ecp /없는파일 /tmp/   # 실패echo "이 줄은 실행되지 않습니다."   # set -e로 여기 오기 전에 종료echo "끝"

set -e 없이 실행하면 에러 메시지가 나와도 두 번째, 세 번째 echo가 모두 출력됩니다. set -e를 켜면 cp가 실패하는 순간 스크립트가 멈춥니다.

set -e의 예외 상황

set -e가 항상 작동하는 것은 아닙니다. 몇 가지 중요한 예외가 있습니다.

#!/usr/bin/env bashset -e# 1. || 뒤에서는 set -e가 적용되지 않음 (의도적 실패 허용)grep "없는문자" /etc/passwd || echo "매치 없음"   # 정상 실행# 2. && 체인에서도 마지막 결과만 확인false && echo "실행 안 됨"   # 이 줄은 전체가 false로 평가# 3. if, while, until 조건 내부if ls /없는경로; then   # ls가 실패해도 스크립트 종료 없음    echo "있음"else    echo "없음"fi# 4. 파이프라인에서 마지막 명령어만 확인 (pipefail 없을 때)cat /없는파일 | sort   # cat이 실패해도 sort가 성공이면 통과

||if 조건에서 실패가 허용되는 것은 사실 의도된 동작입니다. 명령어가 실패할 수 있다는 것을 명시적으로 다루는 코드에서는 set -e가 개입하지 않습니다.

set -e의 함정 — 함수와 서브쉘

함수 내부에서 실패한 명령어가 함수의 반환값으로 사용될 때는 set -e가 예상대로 작동하지 않을 수 있습니다.

#!/usr/bin/env bashset -echeck() {    ls /없는경로   # 실패    return 0}# 함수 결과를 조건으로 사용하면 set -e가 작동하지 않음if check; then    echo "성공"fiecho "스크립트 계속 실행됨"   # 출력됨 (예상과 다름)

이런 이유로 set -e만으로는 완벽하지 않습니다. 중요한 명령어는 직접 exit code를 확인하는 방어 코드를 추가하는 것이 안전합니다.

set -u — 미정의 변수 사용 시 에러

$unset_var를 참조하면 Bash는 기본적으로 빈 문자열로 처리합니다. 오타로 잘못된 변수명을 쓰면 조용히 빈 값을 사용합니다. set -u를 켜면 정의되지 않은 변수를 사용할 때 오류로 처리합니다.

#!/usr/bin/env bashset -u# 오타로 잘못된 변수명BACKUP_DIR="/tmp/backups"rm -rf "$BACKUP_DIER"   # 오타! set -u 없으면 rm -rf ""                         # set -u면 즉시 오류

set -u가 없는 경우 $BACKUP_DIER는 빈 문자열이 되고 rm -rf ""가 실행됩니다. 이것은 실제로 위험한 상황입니다.

${var:-default}와 함께 사용

set -u를 켜면 환경변수가 설정되지 않은 경우도 오류로 처리됩니다. 기본값이 있는 변수는 ${var:-default} 패턴을 사용합니다.

#!/usr/bin/env bashset -u# 환경변수가 없으면 오류echo "$LOG_LEVEL"   # LOG_LEVEL이 없으면 오류# 기본값을 지정하면 안전LOG_LEVEL="${LOG_LEVEL:-INFO}"MAX_RETRY="${MAX_RETRY:-3}"TIMEOUT="${TIMEOUT:-30}"echo "로그 레벨: $LOG_LEVEL"echo "최대 재시도: $MAX_RETRY"echo "타임아웃: $TIMEOUT"

${var:-default}var가 설정되지 않았거나 빈 문자열일 때 default를 사용합니다. ${var:=default}는 같은 조건에서 vardefault를 할당하고 반환합니다. $@, $* 같은 위치 매개변수는 set -u 환경에서도 빈 배열일 때 에러가 나므로 "${@:-}" 또는 "$*" 앞에 인수 개수를 먼저 확인하는 것이 좋습니다.

set -o pipefail — 파이프라인 중간 실패 감지

파이프라인에서 Bash는 기본적으로 마지막 명령어의 종료 코드만 확인합니다. 앞쪽 명령어가 실패해도 마지막이 성공이면 전체 파이프라인이 성공으로 처리됩니다.

#!/usr/bin/env bashset -e   # set -e만 켜고# /없는파일이 없어서 cat이 실패하지만# sort는 성공(빈 입력 처리)이므로 전체가 성공으로 처리됨cat /없는파일 | sort | uniq > /tmp/result.txtecho "파이프라인 종료 코드: $?"   # 0 (잘못된 결과!)

set -o pipefail을 추가하면 파이프라인에서 어떤 명령어든 실패하면 전체 파이프라인을 실패로 처리합니다.

#!/usr/bin/env bashset -eo pipefailcat /없는파일 | sort | uniq > /tmp/result.txtecho "이 줄은 실행되지 않습니다."

pipefail을 켜면 파이프라인 중간의 실패를 잡을 수 있어 데이터 처리 스크립트의 안정성이 크게 높아집니다.

조합 — set -euo pipefail

세 옵션을 따로 쓰는 것보다 한 줄로 조합하는 것이 일반적입니다.

#!/usr/bin/env bashset -euo pipefail

-euo-e -u -o와 같습니다. pipefail-o의 인수로 전달됩니다. 이것이 방어적 Bash 스크립트의 첫 줄입니다.

실습 — set 옵션 유무에 따른 동작 차이 비교

set -euo pipefail이 있을 때와 없을 때 스크립트 동작이 어떻게 달라지는지 직접 비교합니다.

#!/usr/bin/env bash# 새 파일: compare_set_options.sh# 1. set 옵션 없이 실행echo "=== set 옵션 없음 ==="(    TYPO_VAR="hello"    echo "미정의 변수: $TYPO_VAP"   # 오타 변수 → 빈 문자열    ls /없는경로                     # 실패 → 무시    cat /없는파일 | sort              # cat 실패 → sort는 성공    echo "파이프라인 종료 코드: $?"  # 0 출력    echo "스크립트 계속 실행")echo ""# 2. set -euo pipefail로 실행echo "=== set -euo pipefail ==="(    set -euo pipefail    TYPO_VAR="hello"    # echo "미정의 변수: $TYPO_VAP"  # 주석 해제하면 즉시 오류    ls /없는경로 2>/dev/null || echo "ls 실패 (무시하고 계속)"        # 파이프라인 실패 감지    if cat /없는파일 2>/dev/null | sort; then        echo "파이프라인 성공"    else        echo "파이프라인 실패 감지됨 (종료 코드: $?)"    fi        echo "set -euo pipefail 구간 완료")echo ""echo "비교 완료"

실행 결과입니다.

=== set 옵션 없음 ===
미정의 변수:
ls: /없는경로: No such file or directory
파이프라인 종료 코드: 0
스크립트 계속 실행

=== set -euo pipefail ===
ls 실패 (무시하고 계속)
파이프라인 실패 감지됨 (종료 코드: 1)
set -euo pipefail 구간 완료

비교 완료

같은 상황에서 옵션 없이는 오류를 조용히 넘어가고, set -euo pipefail은 각 실패를 잡아냅니다. 이 차이가 새벽 3시에 혼자 돌아가는 배치 스크립트를 신뢰할 수 있게 만드는 핵심입니다.