trap으로 시그널 처리
스크립트가 실행 중에 Ctrl+C를 받으면 어떻게 될까요? 별도 처리가 없다면 그냥 죽습니다. 임시 파일이 있었다면 남고, 진행 중이던 작업은 반쪽짜리로 방치됩니다.
trap은 시그널이 왔을 때 "그냥 죽지 말고 이걸 먼저 해라"고 지시하는 명령어입니다. 스크립트에 trap을 심어두면, 어떤 상황으로 종료되든 지정한 코드가 먼저 실행됩니다.
trap 기본 구문
trap '실행할 명령어' 시그널이름 [시그널이름...]
시그널 이름은 번호(9)로도 쓸 수 있고, 전체 이름(SIGTERM)이나 접두어 없는 이름(TERM)으로도 씁니다.
# Ctrl+C 가로채기trap 'echo "Ctrl+C를 눌렀군요. 종료합니다."' INT# 여러 줄 명령어trap ' echo "종료 신호를 받았습니다." echo "정리 중..."' TERM INT# 함수로 처리 (권장)on_exit() { echo "스크립트가 종료됩니다."}trap on_exit EXIT
명령어가 복잡해지면 함수로 분리하는 것이 훨씬 가독성이 좋습니다.
trap '' SIGNAL: 시그널 무시
빈 문자열을 명령어로 지정하면 그 시그널을 무시합니다.
#!/usr/bin/env bash# Ctrl+C를 무시하는 스크립트trap '' INT # SIGINT 무시echo "Ctrl+C가 작동하지 않습니다."echo "10초 후 종료됩니다."sleep 10echo "완료"
실행 중 Ctrl+C를 눌러도 ^C가 화면에 출력될 뿐 스크립트는 계속 실행됩니다. 중단 없이 실행되어야 하는 중요한 작업에 활용합니다. 단, 사용자가 강제 종료하려면 kill -9를 써야 하므로 남용하면 안 됩니다.
trap - SIGNAL: 기본 동작 복원
trap으로 설정한 처리를 제거하고 시그널의 기본 동작으로 되돌립니다.
# 일부 구간만 Ctrl+C 방지trap '' INTecho "이 구간은 Ctrl+C 불가"# ... 중요한 작업 ...trap - INTecho "이제 Ctrl+C 가능"sleep 10
EXIT: 스크립트 종료 시 항상 실행
EXIT는 시그널이 아니라 Bash의 특수 이벤트입니다. 스크립트가 어떤 이유로 종료되든(정상 종료, 오류, 시그널 수신) EXIT trap은 반드시 실행됩니다.
#!/usr/bin/env bashon_exit() { echo "스크립트 종료. 종료 코드: $?"}trap on_exit EXITecho "작업 시작"# 여기서 오류가 나도, Ctrl+C를 눌러도 on_exit가 실행됨some_commandecho "작업 완료"
EXIT trap 안의 $?는 스크립트가 종료되는 시점의 종료 코드를 담고 있습니다. 성공이면 0, 오류면 그 오류 코드입니다.
여러 시그널 동시 처리
실무에서 가장 많이 사용하는 패턴은 EXIT, ERR, INT, TERM을 함께 trap하는 것입니다.
#!/usr/bin/env bash# 파일: multi_trap.shset -euo pipefailTEMP_FILE=$(mktemp)cleanup() { local exit_code=$? echo "" echo "정리 시작..." rm -f "$TEMP_FILE" echo "임시 파일 삭제 완료" exit "$exit_code"}# 어떤 방식으로 종료되든 cleanup 실행trap cleanup EXIT ERR INT TERMecho "작업 시작. 임시 파일: $TEMP_FILE"# 작업 수행for i in {1..5}; do echo "처리 중: $i/5" echo "데이터 $i" >> "$TEMP_FILE" sleep 1doneecho "작업 완료"
실행하다가 Ctrl+C를 누르면 다음처럼 출력됩니다.
작업 시작. 임시 파일: /tmp/tmp.AbCdEf
처리 중: 1/5
처리 중: 2/5
^C
정리 시작...
임시 파일 삭제 완료
임시 파일이 중간에 남지 않고 깨끗하게 정리됩니다.
ERR trap: 오류 발생 시 처리
ERR은 명령어가 0이 아닌 종료 코드를 반환할 때 실행됩니다. set -e와 함께 사용하면 오류 발생 시점에 즉시 실행됩니다.
#!/usr/bin/env bashset -euo pipefailon_error() { echo "오류 발생! 줄 번호: $LINENO" echo "마지막 명령어 종료 코드: $?"}trap on_error ERRecho "정상 명령어"ls /nonexistent_directory # 오류 발생 → on_error 실행echo "이 줄은 실행되지 않음"
$LINENO는 현재 실행 중인 줄 번호입니다. 오류가 어디서 발생했는지 추적할 때 유용합니다.
실습: Ctrl+C 가로채기
Ctrl+C를 눌렀을 때 즉시 종료하지 않고, 현재 반복 횟수를 저장하고 종료하는 스크립트입니다.
#!/usr/bin/env bash# 파일: trap_demo.shset -uo pipefailcount=0interrupted=falseon_interrupt() { echo "" echo "중단 요청을 받았습니다." echo "현재까지 처리한 항목: $count" interrupted=true}on_exit() { if $interrupted; then echo "사용자에 의해 중단되었습니다." else echo "정상 완료." fi echo "총 처리: $count건"}trap on_interrupt INTtrap on_exit EXITecho "작업 시작. Ctrl+C로 중단할 수 있습니다."while true; do count=$((count + 1)) echo "처리 중: $count..." sleep 1 $interrupted && breakdone
실행 후 Ctrl+C를 누르면 다음처럼 동작합니다.
작업 시작. Ctrl+C로 중단할 수 있습니다.
처리 중: 1...
처리 중: 2...
처리 중: 3...
^C
중단 요청을 받았습니다.
현재까지 처리한 항목: 3
사용자에 의해 중단되었습니다.
총 처리: 3건
단순히 종료되는 것이 아니라 현재 상태를 저장하고 친절한 메시지와 함께 종료됩니다. 이 패턴을 응용하면 배치 처리 스크립트에서 중단 후 재시작 지점을 저장하는 기능도 만들 수 있습니다.
실습: 종료 시 "작업 완료" 메시지 출력
스크립트가 어떻게 종료되든 동일한 완료 메시지와 통계를 남기는 패턴입니다.
#!/usr/bin/env bash# 파일: finish_message.shSTART_TIME=$(date +%s)PROCESSED=0finish() { local end_time end_time=$(date +%s) local elapsed=$((end_time - START_TIME)) echo "" echo "===== 작업 결과 =====" echo "처리 건수: $PROCESSED" echo "소요 시간: ${elapsed}초" echo "종료 시각: $(date)" echo "===================="}trap finish EXIT# 가상 작업 수행for i in {1..10}; do echo "항목 $i 처리 중..." PROCESSED=$((PROCESSED + 1)) sleep 0.5done
EXIT trap에 등록된 finish 함수는 스크립트가 정상 종료되든, 오류로 종료되든, Ctrl+C로 중단되든 항상 실행됩니다. 운영 환경에서 실행되는 배치 스크립트에 이 패턴을 적용하면 로그에 항상 실행 통계가 남습니다.