bash 스크립트의 가장 무서운 점은 에러가 나도 멈추지 않고 다음 줄을 계속 실행한다는 것입니다. cd /backup 이 실패했는데 다음 줄 rm -rf * 가 그대로 돌면 엉뚱한 디렉터리를 지웁니다. 운영 사고 대부분이 여기서 시작됩니다.
기본값이 위험한 이유
아무 설정 없는 bash는 명령이 실패해도 종료 코드를 무시하고 진행합니다.
#!/bin/bash
cd /data/backup # 디렉터리가 없으면 실패
rm -rf ./* # 그래도 실행됨 → 현재 위치에서 삭제
cd 가 실패하면 스크립트는 호출 시점의 작업 디렉터리에 머문 채 rm 을 실행합니다. 실패를 멈추지 않은 대가입니다.
안전 3종 세트 — set -euo pipefail
스크립트 맨 위에 다음 한 줄을 넣는 것이 사실상 표준입니다.
#!/bin/bash
set -euo pipefail
| 옵션 | 효과 | 막아주는 사고 |
|---|---|---|
set -e | 명령이 실패(0이 아닌 종료)하면 즉시 중단 | 실패 후 다음 줄 강행 |
set -u | 정의되지 않은 변수 참조 시 에러 | 오타 변수가 빈 값으로 치환 |
set -o pipefail | 파이프 중 하나라도 실패하면 전체 실패 처리 | `cmd |
set -u 가 없으면 rm -rf "$PREFIX/cache" 에서 PREFIX 오타가 빈 값이 되어 rm -rf /cache 로 돌변합니다. pipefail 이 없으면 curl ... | grep ok 에서 curl 이 죽어도 파이프라인은 grep 의 종료 코드만 봐서 성공으로 오인합니다.
set -e 의 함정과 회피
set -e 는 만능이 아닙니다. 일부러 실패를 허용해야 하는 곳에서 스크립트가 죽어버립니다.
# grep이 매칭 못 하면 종료코드 1 → set -e가 스크립트를 죽임
if grep -q "ERROR" app.log; then
echo "에러 발견"
fi
# 조건문 안에서는 set -e가 적용되지 않으므로 안전
# 실패를 허용하고 싶을 때
some_optional_cmd || true
조건문(if, while, &&, ||) 안의 명령은 set -e 대상에서 제외되므로, 실패가 정상인 검사는 반드시 조건문이나 || true 로 감쌉니다.
작성 전 체크리스트
set -euo pipefail # 맨 위 고정
trap 'echo "실패: line $LINENO"' ERR # 어디서 죽었는지 출력
"${VAR:?VAR가 설정되지 않음}" # 필수 변수 강제
bash -n script.sh # 실행 전 문법 검사
shellcheck script.sh # 정적 분석으로 함정 사전 탐지
특히 trap ... ERR 로 실패 지점을 로그에 남기면, set -e 로 조용히 멈춘 스크립트도 어느 줄에서 죽었는지 바로 알 수 있습니다.
set -euo pipefail 부터 trap, shellcheck 까지 방어적 bash 작성을 직접 실습하고 운영 스크립트 감각을 익히는 과정은 리눅스 트랙에서 회원가입 없이 무료로 시작할 수 있습니다.