종료 코드 이해하기: `$?` 및 `exit`를 사용한 효과적인 오류 처리

종료 코드(성공은 0, 실패는 0이 아닌 값)를 이해하여 Bash 오류 처리를 마스터하세요. 이 필수 가이드는 특수 변수 `$?`를 사용하여 마지막 명령어의 상태를 검사하고, 의도적인 스크립트 종료를 위해 `exit` 명령어를 활용하는 방법을 상세히 설명합니다. 강력하고 자가 진단 기능을 갖춘 자동화 스크립트를 구축하기 위해 `set -e`와 조건부 논리(`&&`, `||`)를 사용한 모범 사례를 배우세요.

39 조회수

종료 코드 이해하기: $?와 exit를 활용한 효과적인 오류 처리

자동화 및 쉘 스크립팅의 세계에서 스크립트가 실패한 이유를 아는 것은 실패했다는 사실을 아는 것만큼이나 중요합니다. Bash 스크립팅은 성공 또는 실패를 보고하기 위한 표준화된 메커니즘, 즉 종료 코드(exit codes), 종료 상태(exit statuses) 또는 반환 코드(return codes)라고도 알려진 것에 크게 의존합니다. 이러한 코드가 어떻게 작동하는지, 특수 변수 $?를 사용하여 어떻게 검사하는지, exit 명령을 사용하여 의도적으로 스크립트를 종료하는 방법을 이해하는 것은 견고하고 안정적이며 디버깅 가능한 자동화를 작성하는 데 기초가 됩니다.

이 가이드에서는 종료 코드의 개념을 분석하고, Bash가 이를 자동으로 추적하는 방법을 시연하며, 쉘 스크립트 내에서 효과적인 오류 검사를 구현하는 실용적인 기술을 보여줄 것입니다. 이를 마스터하면 자동화 파이프라인이 우아하게 실패하고 의미 있는 피드백을 제공하도록 보장할 수 있습니다.

종료 상태의 개념

Unix 계열 쉘 환경에서 실행되는 모든 명령 또는 프로그램—cd와 같은 내장 명령이든, grep과 같은 외부 유틸리티든, 다른 쉘 스크립트든—는 완료 시 정수 값을 반환합니다. 이 정수가 종료 코드이며, 작업의 결과를 호출 프로세스에 신호로 보냅니다.

표준 규약

종료 코드에 대한 규약은 보편적으로 인식됩니다:

  • 0 (영): 성공을 의미합니다. 명령은 예상대로 정확하게 실행되었으며 오류가 발생하지 않았습니다.
  • 1~255: 실패 또는 특정 오류 조건을 의미합니다. 이러한 0이 아닌 값은 무언가 잘못되었음을 나타냅니다. 더 높은 숫자는 종종 특정 유형의 오류(예: 파일을 찾을 수 없음, 권한 거부, 구문 오류)에 해당하지만, 정확한 의미는 특정 프로그램에 따라 다릅니다.

범위에 대한 참고: 종료 코드는 기술적으로 8비트 값(0-255)이지만, 쉘 스크립트는 일반적으로 성공에는 0을, 실패에는 0이 아닌 값을 사용하는 데 중점을 둡니다. 255보다 큰 종료 코드는 일반적으로 쉘에 의해 잘리거나 모듈로 256으로 해석됩니다.

마지막 종료 코드 검사: $? 변수

특수 쉘 변수 $?(달러 물음표)는 명령 상태 모니터링의 핵심입니다. 모든 명령이 실행된 직후, 쉘은 해당 종료 코드를 $?에 저장합니다.

$? 사용 방법

관심 있는 명령 직후에 $?즉시 확인해야 합니다. 왜냐하면 변수를 에코하는 것과 같은 후속 명령은 해당 값을 덮어쓸 수 있기 때문입니다.

예제 1: 성공 및 실패 확인

# 1. 성공적인 명령
echo "Success test" > /dev/null
echo "성공 종료 코드: $?"

# 2. 실패하는 명령 (예: 존재하지 않는 파일 목록 시도)
ls /non/existent/path
echo "실패 종료 코드: $?"

예상 출력:

성공 종료 코드: 0
ls: cannot access '/non/existent/path': No such file or directory
실패 종료 코드: 2

조건부 오류 처리 구현

종료 코드를 아는 것만으로는 충분하지 않습니다. 이 정보를 사용하여 스크립트 흐름을 제어하는 데서 강력함이 나옵니다. 이는 일반적으로 if 문 또는 단축 논리 연산자(&&||)를 사용하여 수행됩니다.

if 문 사용

이것은 오류를 처리하는 가장 명시적인 방법입니다:

if grep -q "important data" logfile.txt;
then
    echo "데이터를 성공적으로 찾았습니다."
else
    LAST_STATUS=$?
    echo "오류: Grep이 상태 $LAST_STATUS로 실패했습니다. 데이터를 찾을 수 없습니다."
    # 스크립트가 계속 진행될 수 없다면 여기서 종료하는 것을 고려하세요.
fi

위 예제에서 grep -q는 출력을 억제하고(-q) 일치하는 항목이 발견된 경우에만 0을 반환합니다. if 구조는 종료 상태를 자동으로 확인하지만, else 블록 내에서 명시적으로 $?를 캡처하는 것은 자세한 로깅에 유용합니다.

단축 논리 (&&||) 사용

간단한 순차적 검사의 경우, 단축 연산자는 간결한 오류 처리를 제공합니다:

  • && (AND): && 뒤의 명령은 이전 명령이 성공한 경우(0 반환)에만 실행됩니다.
  • || (OR): || 뒤의 명령은 이전 명령이 실패한 경우(0이 아닌 값 반환)에만 실행됩니다.

예제 2: 간결한 오류 처리

# 1. 'fetch_data'가 성공하는 경우에만 'process_data' 실행
fetch_data.sh && ./process_data.sh

# 2. 주 작업이 실패하는 경우에만 'send_alert' 실행
rsync -a source/ dest/ || echo "RSync failed on $(date)" >> /var/log/rsync_errors.log

exit로 스크립트 종료 제어

exit 명령은 현재 쉘 스크립트 또는 함수를 즉시 종료하고 지정된 종료 상태를 호출자(다른 스크립트 또는 사용자의 터미널일 수 있음)에게 반환하는 데 사용됩니다.

구문 및 사용법

구문은 간단히 exit [status_code]입니다.

상태를 제공하지 않으면 exit는 가장 최근에 실행된 포그라운드 명령의 상태를 기본값으로 사용합니다. 아무 명령도 먼저 실행하지 않고 명시적으로 exit 0을 호출하면 0을 반환합니다.

예제 3: 사전 조건 실패 시 종료

이 스크립트는 계속 진행하기 전에 필요한 구성 파일이 있는지 확인합니다.

CONFIG_FILE="/etc/app/config.conf"

if [[ ! -f "$CONFIG_FILE" ]]; then
    echo "오류: 구성 파일을 $CONFIG_FILE에서 찾을 수 없습니다."
    # 특정 오류 코드(예: 20)로 즉시 스크립트 종료
    exit 20 
fi

echo "구성이 로드되었습니다. 스크립트를 계속합니다..."
# ... 스크립트의 나머지 부분
exit 0

모범 사례: 의미 있는 종료 코드 사용

01은 대부분의 기본 경우를 다루지만, 다른 0이 아닌 코드를 사용하면 호출 스크립트가 정확한 문제를 진단하는 데 도움이 됩니다:

| 코드 | 의미 (예시) |
| :--- |
| 0 | 성공 |
| 1 | 일반적인 오류 |
| 2-10 | 구문 오류, 인수 파싱 문제 |
| 20 | 필수 요소 누락 (예: 파일을 찾을 수 없음) |
| 30 | 권한 문제 |

스크립트의 빠른 실패: set 명령

복잡한 스크립트에서 최대의 신뢰성을 위해, 스크립트 상단에 set 명령 옵션을 사용하여 오류 검사를 전역적으로 활성화하는 것이 강력한 모범 사례입니다:

#!/bin/bash

# 명령이 0이 아닌 상태로 종료되면 즉시 종료합니다.
set -e

# 대체 시 정의되지 않은 변수를 오류로 처리합니다.
set -u

# Pipefail: 파이프라인의 반환 상태가 0이 아닌 상태로 종료된 가장 오른쪽 명령의 상태가 되도록 보장합니다.
set -o pipefail

# (선택 사항이지만 유용함) 디버깅을 위해 실행되는 명령을 인쇄합니다.
# set -x 

# 아래의 어떤 명령이든 실패하면 스크립트가 즉시 중지됩니다.
ls /valid/path && grep pattern file.txt && ./next_step.sh

# 다음 줄은 앞의 모든 명령이 성공한 경우에만 실행됩니다.
echo "모든 단계가 완료되었습니다."

set -e가 활성화되면 스크립트 실행은 첫 번째 0이 아닌 종료 상태에서 자동으로 중단되어, 잘못된 중간 데이터에 기반한 후속 명령의 실행을 방지합니다.