Bash 스크립트 디버깅 마스터하기: 개발자를 위한 필수 기술

구문 검사, xtrace, 엄격 모드, 트랩, ShellCheck 및 집중 로깅을 사용하여 Bash 스크립트를 디버그합니다.

Bash 스크립트 디버깅 마스터하기: 개발자를 위한 필수 기술

Bash 스크립트 디버깅은 한 가지 질문으로 시작됩니다: 스크립트가 예상과 다르게 동작한 지점은 어디인가요? 좋은 디버깅은 스크립트를 소음으로 만들지 않으면서 구문 오류, 확장된 변수, 명령 순서 및 종료 상태를 파악할 수 있게 해줍니다.

이 가이드는 cron, CI 또는 프로덕션에 도달하기 전에 실제 자동화 스크립트에서 사용할 수 있는 실용적인 Bash 디버깅 기술을 안내합니다.

구문 검사로 시작하기

런타임 동작을 추적하기 전에 Bash가 파일을 구문 분석할 수 있는지 확인하세요:

bash -n ./deploy.sh

bash -n은 명령을 실행하지 않고 스크립트를 읽고 구문 오류를 보고합니다. 누락된 fi, done, then, 따옴표 및 중괄호를 잡아냅니다. 논리 오류, 누락된 파일 또는 런타임에 실패하는 명령은 잡지 못합니다.

예를 들어, 이 오타는 아무것도 실행되기 전에 잡힙니다:

if [ -f "$CONFIG" ]; then
    echo "Config found"
# missing fi

큰 편집 후와 더 많은 디버깅 출력을 추가하기 전에 구문 검사를 실행하세요.

set -x로 실행 추적하기

가장 유용한 내장 디버거는 xtrace입니다:

set -x
some_command "$VALUE"
set +x

추적이 활성화되면 Bash는 확장 후 실행 전에 각 명령을 출력합니다. 이는 변수가 비어 있는지, 글로브가 확장되었는지, 명령이 예상과 다른 인수를 받았는지 확인하는 데 도움이 됩니다.

전체 스크립트 추적의 경우 다음을 실행하세요:

bash -x ./deploy.sh

더 깔끔한 추적을 위해 PS4를 설정하여 각 줄에 소스 줄 번호를 포함하세요:

export PS4='+ ${BASH_SOURCE}:${LINENO}: '
bash -x ./deploy.sh

스크립트가 비밀을 처리하는 경우 비밀번호, 서명된 URL 또는 토큰을 출력하는 섹션을 추적하지 마세요. 해당 명령 전에 추적을 끄세요:

set +x
login_with_secret "$API_TOKEN"
set -x

엄격 모드 주의해서 추가하기

다음 옵션은 일반적인 실패를 더 일찍 잡아냅니다:

set -euo pipefail

set -e는 처리되지 않은 많은 명령 실패 시 종료됩니다. set -u는 설정되지 않은 변수를 오류로 처리합니다. set -o pipefail은 파이프라인의 마지막 명령뿐만 아니라 파이프라인의 어떤 명령이 실패하면 파이프라인이 실패하도록 만듭니다.

이들은 유용하지만 명시적 처리를 대체하지는 않습니다. grep과 같은 명령은 정상적인 "찾을 수 없음" 결과에 대해 1을 반환할 수 있습니다:

if grep -q "READY" status.txt; then
    echo "ready"
else
    echo "not ready"
fi

이는 grep -q "READY" status.txt || true로 결과를 숨기는 것보다 명확합니다.

올바른 값 출력하기

집중 로깅은 흩어진 echo 줄보다 낫습니다. 디버깅 중인 분기에 영향을 미치는 값을 출력하세요:

printf 'DEBUG: user=%q env=%q target=%q\n' "$USER_NAME" "$ENVIRONMENT" "$TARGET_HOST" >&2

printf '%q'는 셸 이스케이프된 값을 표시하여 공백과 특수 문자를 더 쉽게 찾을 수 있게 합니다. 디버그 출력을 stderr로 보내면 일반 스크립트 출력이 파이프라인에서 계속 사용 가능합니다.

명령이 실패하면 즉시 상태를 캡처하세요:

run_migration
status=$?

if [ "$status" -ne 0 ]; then
    echo "Migration failed with exit code $status" >&2
    exit "$status"
fi

$?를 저장하기 전에 다른 명령을 실행하지 마세요. echo조차도 이를 대체합니다.

루프와 조건문 디버깅하기

루프 버그는 종종 단어 분할이나 예상치 못한 입력에서 발생합니다. 변수를 인용하고 안전하게 줄을 읽으세요:

while IFS= read -r line; do
    printf 'line=%q\n' "$line" >&2
done < input.txt

조건문의 경우 비교되는 정확한 값을 출력하세요:

printf 'expected=%q actual=%q\n' "$EXPECTED" "$ACTUAL" >&2

if [[ "$ACTUAL" == "$EXPECTED" ]]; then
    echo "match"
fi

로컬 디버깅 중에 스크립트 내에서 일시 중지해야 하는 경우 read가 작동합니다:

read -r -p "Press Enter to continue..."

스크립트를 커밋하기 전에 일시 중지를 제거하세요, 특히 무인 실행될 수 있는 경우.

정적 분석을 위해 ShellCheck 사용하기

ShellCheck는 Bash가 모서리 경우에서 깨질 때까지 행복하게 실행할 많은 문제를 잡아냅니다:

shellcheck ./deploy.sh

인용되지 않은 변수, 도달할 수 없는 코드, 의심스러운 테스트, 사용되지 않은 변수 및 이식성 문제를 플래그합니다. 경고를 스크립트가 잘못되었다는 자동 증거가 아니라 코드를 검사하라는 프롬프트로 취급하세요. 때로는 의도적으로 경고를 비활성화할 수 있지만, 이유를 설명하는 짧은 주석을 추가하세요.

trap을 사용하여 실패한 줄 확인하기

더 긴 스크립트의 경우 오류 트랩이 실패가 발생한 위치를 알려줄 수 있습니다:

set -Eeo pipefail

trap 'echo "Error on line $LINENO: $BASH_COMMAND" >&2' ERR

set -E는 Bash에서 ERR 트랩이 함수와 서브쉘로 전파되도록 돕습니다. 이는 대화형 셸이 없을 수 있는 CI 로그에서 유용합니다.

요점

bash -n으로 시작하고, 런타임 추적을 위해 bash -x 또는 대상 set -x를 사용하고, 잘못 동작하는 분기 주변에 집중된 stderr 로깅을 추가하세요. 중요한 스크립트의 경우 ShellCheck를 실행하고 ERR 트랩을 추가하여 실패가 주의가 필요한 명령과 줄을 가리키도록 하세요.