고급 Bash 스크립팅: 오류 처리를 위한 모범 사례

엄격 모드, 명시적 검사, 정리 트랩, 명확한 종료 코드 및 stderr 로깅을 통해 Bash 오류 처리를 개선합니다.

고급 Bash 스크립팅: 오류 처리를 위한 모범 사례

Bash 스크립팅 오류 처리는 작은 자동화 실수가 지저분한 프로덕션 문제가 되는 것을 방지합니다. 백업이 실패하거나, API 호출이 오류를 반환하거나, 임시 파일이 남겨지면 스크립트가 명확하게 중지되고 시스템을 알려진 상태로 유지해야 합니다.

스크립트가 파일을 변경하거나, 코드를 배포하거나, 원격 서비스와 통신하거나, 터미널을 감시하는 사람 없이 실행될 때 이러한 패턴을 사용하십시오.

기본 사항: 종료 코드 이해

Bash에서 실행되는 모든 명령은 성공 또는 실패 여부에 관계없이 종료 상태(또는 종료 코드)를 반환합니다. 이는 명령 결과를 알리는 기본 메커니즘입니다.

  • 종료 코드 0: 성공적인 실행을 나타냅니다. 관례상 0은 성공을 의미합니다.
  • 종료 코드 1-255(0이 아님): 오류, 실패 또는 경고를 나타냅니다. 특정 0이 아닌 코드는 종종 특정 오류 유형을 나타냅니다(예: 1은 일반적으로 일반 오류, 2는 종종 셸 명령의 잘못된 사용을 의미).

가장 최근의 종료 상태는 특수 변수 $?에 저장됩니다.

# 성공적인 명령
ls /tmp
echo "상태: $?"
# 상태: 0

# 실패한 명령(존재하지 않는 파일)
cat /nonexistent_file
echo "상태: $?"
# 상태: 1(또는 오류에 따라 더 높음)

필수 모범 사례: 엄격 모드 구현

모든 중요한 Bash 스크립트의 경우, 셔뱅 라인 바로 뒤에 세 가지 지시문을 배치해야 합니다. 이들을 통틀어 종종 "엄격 모드"라고 합니다. 이들은 스크립트가 깨진 전제 조건 후에 계속 실행되는 대신 조기에 실패하도록 강제합니다.

1. 오류 발생 시 즉시 종료(set -e)

set -e 또는 set -o errexit 명령은 명령이 0이 아닌 상태로 종료되면 Bash가 즉시 스크립트를 종료하도록 지시합니다. 이는 연속적인 실패를 방지합니다.

경고: set -e는 조건부 테스트(if, while) 또는 명령이 && 또는 || 목록의 일부인 경우 무시됩니다. 실패 상태는 주변 구조에서 명시적으로 사용되어야 합니다.

2. 설정되지 않은 변수를 오류로 처리(set -u)

set -u 또는 set -o nounset 명령은 스크립트가 설정되지 않은 변수(예: $FILENAME 대신 $FIELNAME 오타)를 사용하려고 하면 즉시 종료되도록 합니다. 이는 빈 변수나 의도하지 않은 변수로 인해 디버깅하기 어려운 버그를 방지합니다.

3. 파이프라인에서 오류 처리(set -o pipefail)

기본적으로 일련의 명령이 함께 파이프되면(예: cmd1 | cmd2 | cmd3), Bash는 마지막 명령(cmd3)의 종료 상태만 보고합니다. cmd1이 실패하면 스크립트가 계속 성공적으로 실행될 수 있습니다.

set -o pipefail은 파이프라인의 종료 상태가 실패한 마지막 명령의 종료 상태이거나 모든 명령이 성공한 경우 0이 되도록 합니다. 이는 안정적인 데이터 처리에 중요합니다.

표준 엄격 모드 헤더

고급 스크립트는 항상 이 강력한 헤더로 시작하십시오:

#!/bin/bash

set -euo pipefail

일부 이전 템플릿은 IFS=$'\n\t'도 설정합니다. 이것이 스크립트의 나머지 부분에서 단어 분할에 어떤 영향을 미치는지 이해하는 경우에만 사용하십시오. 변수를 인용하고 while IFS= read -r line으로 입력을 읽는 것이 일반적으로 더 명확합니다.

조건부 오류 검사

set -e가 예상치 못한 오류를 처리하는 반면, 특정 조건을 확인하거나 사용자 정의 오류 메시지를 제공해야 하는 경우가 많습니다.

if 문 및 사용자 정의 함수 사용

set -e에만 의존하는 대신 if 블록을 사용하여 알려진 잠재적 실패를 적절하게 처리하고 설명적인 출력을 제공하십시오.

# 일관성을 위한 사용자 정의 오류 함수 정의
error_exit() {
    printf '[치명적 오류] %s\n' "$1" >&2
    exit 1
}

TEMP_DIR="/tmp/data_processing_$(date +%s)"

# 디렉토리 생성이 성공했는지 확인
if ! mkdir -p "$TEMP_DIR"; then
    error_exit "임시 디렉토리 생성 실패: $TEMP_DIR"
fi

echo "임시 디렉토리가 성공적으로 생성되었습니다: $TEMP_DIR"

# 처리 전 파일 존재 여부 확인 예시
FILE_TO_PROCESS="input.csv"

if [[ ! -f "$FILE_TO_PROCESS" ]]; then
    error_exit "입력 파일을 찾을 수 없음: $FILE_TO_PROCESS"
fi

단축 논리(&&||)

간단한 순차 작업의 경우 단축 연산자를 사용하십시오. 이는 가독성이 높고 간결합니다.

  • 성공 체인(&&): 첫 번째 명령이 성공한 경우에만 두 번째 명령이 실행됩니다.
  • 실패 포착(||): 첫 번째 명령이 실패한 경우에만 두 번째 명령이 실행됩니다.
# 설정을 실행한 다음 처리, 설정 실패 시 실패
setup_environment && process_data

# 연결 시도, 실패 시 메시지와 함께 정상 종료
ssh user@server || { echo "연결 실패, 네트워크 설정을 확인하세요." >&2; exit 2; }

trap을 사용한 정상 종료 및 정리

trap 명령을 사용하면 스크립트가 신호(Ctrl+C, 시스템 종료 또는 스크립트 종료 등)를 포착하고 종료 전에 지정된 명령이나 함수를 실행할 수 있습니다. 이는 정리 작업에 필수적입니다.

cleanup 함수

변경 사항을 되돌리기 위한 전용 함수(예: 임시 파일 삭제, 구성 재설정)를 정의하고 trap을 사용하여 스크립트가 어떻게 종료되든 관계없이 실행되도록 합니다.

# 정리 함수가 확인할 전역 변수
TEMP_FILE=""

cleanup() {
    printf '%s\n' "--- 정리 절차 실행 중 ---"
    if [[ -f "$TEMP_FILE" ]]; then
        rm -f "$TEMP_FILE"
        echo "임시 파일 삭제됨: $TEMP_FILE"
    fi
    # 선택적으로 최종 종료 상태 보고서 제공
}

# 1. EXIT 트랩: 성공, 실패 또는 신호에 관계없이 정리 실행
trap cleanup EXIT

# 2. 신호 트랩(INT=Ctrl+C, TERM=종료 신호)
trap 'printf "%s\n" "사용자 또는 시스템 신호로 인해 스크립트가 중단되었습니다." >&2; exit 130' INT
trap 'printf "%s\n" "스크립트가 종료되었습니다." >&2; exit 143' TERM

# --- 메인 스크립트 로직 ---
TEMP_FILE=$(mktemp)
echo "임시 내용" > "$TEMP_FILE"
# 여기서 스크립트가 실패하거나 중단되면 cleanup()이 실행되는 것이 보장됩니다.

trap cleanup EXIT를 사용하는 이유?

EXITtrap을 설정하면 스크립트가 정상적으로 종료되거나(exit 0), 명시적으로 오류와 함께 종료되거나(exit 1), set -e로 인해 강제 종료되더라도 정리 함수가 실행되도록 보장합니다.

고급 오류 보고

표준 오류 메시지(command not found)는 종종 컨텍스트가 부족합니다. 고급 스크립트는 무엇이 실패했는지, 어디서 실패했는지, 실패했는지 보고해야 합니다.

줄 번호 기록

error_exit와 같은 함수가 호출되면 BASH_LINENO 배열 또는 caller 명령을 사용하여 오류가 발생한 스크립트 내의 줄 번호를 확인할 수 있습니다(caller는 종종 함수로 제한됨).

함수 외부의 간단한 보고를 위해 LINENO 변수를 사용하십시오:

# 즉시 실패 보고 예시
(some_risky_command) || {
    echo "[$LINENO 오류] some_risky_command가 상태 $?로 실패했습니다" >&2
    exit 3
}

출력 구분

정보 메시지는 항상 표준 출력(stdout)으로 보내고 오류/경고 메시지는 표준 오류(stderr)로 보내십시오. 이는 스크립트의 출력이 다른 프로그램으로 파이프되거나 외부에 기록되는 경우 중요합니다.

  • echo "정보 메시지" (stdout으로 이동)
  • echo "[경고] 구성 재정의" >&2 (stderr로 이동)

패턴 종합하기

대부분의 프로덕션 스크립트에서 실용적인 패턴은 간단합니다. set -euo pipefail로 시작하고, 작업을 수행하기 전에 입력의 유효성을 검사하고, 예상되는 실패를 if ! command; then ...; fi로 감싸고, 임시 상태를 만들기 전에 trap cleanup EXIT를 추가하십시오.

이렇게 하면 미스터리 실패 대신 유용한 실패가 발생합니다. 다음에 오전 2시에 작업이 중단되면 로그에 무엇이 실패했는지, 어디를 봐야 하는지, 정리가 실행되었는지 표시되어야 합니다.