Computer >> 컴퓨터 >  >> 프로그래밍 >> Bash 프로그래밍

Bash trap 지시어로 스크립트 종료 제어하기

셸 스크립트가 시작되는 시점을 감지하는 것은 쉽지만, 언제 종료되는지 파악하는 것은 항상 쉬운 일이 아닙니다. 스크립트는 작성자가 의도한 대로 정상적으로 끝날 수도 있지만, 예기치 못한 치명적인 오류로 인해 실패할 수도 있습니다. 때로는 스크립트가 실패했을 때 진행 중이던 작업의 흔적을 보존하는 것이 유익하고, 또 어떤 경우에는 그것이 오히려 불편함을 초래하기도 합니다. 어느 쪽이든 스크립트의 종료를 감지하고 사전에 설계된 방식으로 대응할 수 있게 해주는 것이 바로 Bash의 trap 지시어입니다.

오류에 대응하기

다음은 스크립트 내부의 하나의 오류가 이후의 연쇄적인 오류로 이어질 수 있는 과정을 보여주는 예제입니다. /tmp에 임시 디렉터리를 생성하여 파일의 압축을 풀고 처리한 뒤, 다른 형식으로 다시 묶어내는 프로그램을 작성했다고 가정해 보겠습니다:

#!/usr/bin/env bash
CWD=`pwd`
TMP=${TMP:-/tmp/tmpdir}

## create tmp dir
mkdir "${TMP}"

## extract files to tmp
tar xf "${1}" --directory "${TMP}"

## move to tmpdir and run commands
pushd "${TMP}"
for IMG in *.jpg; do
  mogrify -verbose -flip -flop "${IMG}"
done
tar --create --file "${1%.*}".tar *.jpg

## move back to origin
popd

## bundle with bzip2
bzip2 --compress "${TMP}"/"${1%.*}".tar \
      --stdout > "${1%.*}".tbz

## clean up
/usr/bin/rm -r /tmp/tmpdir

대부분의 경우 이 스크립트는 기대한 대로 작동합니다. 하지만 JPEG 파일이 아닌 PNG 파일로 가득 찬 아카이브에 실수로 실행하면 중간에 실패하게 됩니다. 한 번의 오류가 또 다른 오류를 낳고, 결국 스크립트는 임시 디렉터리를 삭제하는 마지막 명령에 도달하지 못한 채 종료됩니다. 직접 해당 디렉터리를 삭제해 주면 빠르게 복구할 수 있지만, 그럴 수 없는 상황이라면 다음번 스크립트 실행 시 예측할 수 없는 잔여 파일들로 가득 찬 기존 임시 디렉터리를 처리해야 하는 문제에 직면하게 됩니다.

이를 해결하는 한 가지 방법은 스크립트 시작 부분에 예방 차원의 삭제 명령을 추가하는 것입니다. 물론 유효한 방법이지만, 체계적인 구조가 아닌 단순 무식한 방식에 의존하게 됩니다. 더 우아한 해결책이 바로 trap입니다.

trap으로 시그널 포착하기

trap 키워드는 실행 중에 발생할 수 있는 시그널(signal)을 포착합니다. kill이나 killall 명령어를 사용해 본 경험이 있다면 이미 이러한 시그널 중 하나를 사용한 것입니다. 두 명령어는 기본적으로 SIGTERM을 호출합니다. 셸이 반응하는 시그널은 매우 다양하며, trap -l(list의 약자) 옵션으로 대부분의 목록을 확인할 수 있습니다:

$ trap --list
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL      5) SIGTRAP
 6) SIGABRT      7) SIGBUS      8) SIGFPE      9) SIGKILL    10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE    14) SIGALRM    15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT    19) SIGSTOP    20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG     24) SIGXCPU    25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH   29) SIGIO      30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX

이 시그널들은 모두 trap으로 미리 감지할 수 있습니다. 이 외에도 trap은 다음과 같은 조건들을 인식합니다:

  • EXIT: 셸 프로세스 자체가 종료될 때 발생
  • ERR: 명령어(tarmkdir 등) 또는 내장 명령(pushdcd 등)이 0이 아닌 상태 코드로 종료될 때 발생
  • DEBUG: 디버그 모드를 나타내는 부울 값

Bash에서 트랩을 설정하려면 trap 뒤에 실행할 명령어 목록을 작성하고, 그 뒤에 트리거 역할을 할 시그널 목록을 나열하면 됩니다.

기본적인 트랩 설정 예시

예를 들어, 다음 트랩은 프로세스 실행 중 사용자가 Ctrl+C를 누를 때 전송되는 SIGINT를 감지합니다:

trap "{ echo 'Terminated with Ctrl+C'; }" SIGINT

앞서 살펴본 임시 디렉터리 문제가 있는 예제 스크립트는 SIGINT와 오류, 그리고 성공적인 종료까지 감지하는 트랩으로 수정할 수 있습니다:

#!/usr/bin/env bash
CWD=`pwd`
TMP=${TMP:-/tmp/tmpdir}

trap \
 "{ /usr/bin/rm -r "${TMP}" ; exit 255; }" \
 SIGINT SIGTERM ERR EXIT

## create tmp dir
mkdir "${TMP}"
tar xf "${1}" --directory "${TMP}"

## move to tmp and run commands
pushd "${TMP}"
for IMG in *.jpg; do
  mogrify -verbose -flip -flop "${IMG}"
done
tar --create --file "${1%.*}".tar *.jpg

## move back to origin
popd

## zip tar
bzip2 --compress $TMP/"${1%.*}".tar \
      --stdout > "${1%.*}".tbz

복잡한 작업을 처리해야 한다면 Bash 함수를 활용해 trap 문을 더욱 간결하게 정리할 수 있습니다.

Bash에서의 트랩 활용

트랩은 스크립트가 성공하든 실패하든 깔끔하게 종료되도록 보장하는 데 유용한 도구입니다. 자동화된 가비지 컬렉션만 믿고 방치하는 것은 결코 안전하지 않으므로, 트랩 활용은 습관 들일 만한 좋은 관행입니다. 여러분의 스크립트에 직접 적용해 보고 그 효과를 확인해 보세요!