쉘기능을 자유자재로 다루기_09 명령어가 실패한 시점에 종료해 스크립트 오작동 방지하기

 
  • 출처 : 유닉스 리눅스 쉘스크립트 예제사전_한빛미디어

명령어: set, cd, rm
키워드: 종료 스테이터스, 명령어, 실패, 정지
사용처: 어떤 중요한 처리를 하는 스크립트에서 도중에 명령어가 하나라도 실패하면 거기서 정지하고 싶을 때


실행예제

$ ./set-e.sh
./set-e.sh: line 12: cd: /var/log/myapp-: No Such file or directory

$ ls
set-e.sh test.log               (test.log가 남아 있음)

스크립트

#!/bin/sh

# 명령어 종료 스테이터스가 0이 아니라면
# 스크립트를 바로 종료하기
set -e  #------------------------------------------------------ 1

# 삭제 파일이 있는 디렉터리(일부러 틀림)
deldir="/var/log/myapp-"  #------------------------------------ 2

# 디렉터리 $deldir로 이동해서 확장자가 .log인 파일 삭제
# set -e 때문에 디렉터리 이동에 실패하면 rm 명령어가 실행되지 않음
cd "$deldir"  #------------------------------------------------ 3
rm -f *.log  #------------------------------------------------- 4

   

해설

이 스크립트는 셀 변수 deldir로 지정한 디렉터리 안에 있는 확장자가 .log인 파일을 모두 삭제합니다. 이 스크립트의 목적은 정기적으로 실행해서 특정 애플리케이션의 오래된 로그 파일을 삭제하여 디스크 공간을 확보하는 것이라고 가정합니다. 예제에서는 명령어가 실패하면 거기서 스크립트 실행을 정지하고 그 이후로 처리가 진행되지 않도록 하는 것이 중요합니다.

셸 스크립트에서는 스크립트 파일에 적힌 순서대로 위에서부터 명령어를 실행합니다. 그때 도중에 명령어가 성공했는지 실패했는지 신경 쓰지 않습니다. 즉, 앞의 명령어가 성공했다는 전제로 스크립트를 작성했다면 사실 그전 명령어에 실패해서 생각하지 못한 버그가 생길 수도 있습니다.

그런 문제를 해결하려면 set 명령어 -e 옵션을 사용합니다. 그 옵션을 사용하면 도중에 종료 스테이터스가 0이 아닌 명령어가 있으면 거기서 스크립트를 종료합니다.

예제에서는 우선 1에서 set -e 옵션을 실행합니다. set -e는 일반적으로 이런 스크립트 앞부분에 적는 것이 보통인데 스크립트 특정 행 이후만 확인하고 싶다면 도중에 set -e를 지정하는 경우도 있습니다.

2에서 로그 파일을 삭제하는 디렉터리를 지정합니다. 여기서 대상 디렉터리를 잘못 타이핑한 경우를 가정하므로 “/var/log/myapp”이 원래 지정하려던 디렉터리인데 “/var/log/myapp-“라고 작성했다고 합시다.

2은 존재하지 않는 디렉터리로 이동하므로 cd 명령어에서 에러가 발생하여 종료 스테이터스는 0이 아닌 값이 됩니다. 스크립트 첫 부분에 set -e하고 있으므로 여기서 바로 스크립트가 종료됩니다. 결과로 .log 파일을 삭제하는 rm 명령어 4는 실행되지 않습니다. 만약 set -e가 없으면 4는 cd 명령어에 실패해도 그대로 실행되어서 현재 디렉터리의 .log 파일을 삭제합니다.

이렇게 하면 명령어가 도중에 실패했을 때 스크립트를 바로 정지할 수 있습니다. 파일 삭제를 하는 스크립트라면 오동작하지 않도록 이렇게 작성하는게 좋습니다.

set -e 설명

일반적인 스크립트에서 “명령어가 성공”이라는 것은 명령어 종료 스테이터스가 0이라는 뜻입니다. 명령어 종료 스테이터스는 셸 특수 변수 $?에 대입되므로 이 값을 참조하면 성공과 실패를 판별할 수 있습니다.

set 명령어 -e 옵션은 명령어 종료 스테이터스가 0이 아니면 뭐든지 종료합니다. 주의해야 하는 것은 파일 비교를 위해 diff 명령어로 다음과 같이 작성해도 diff 명령어는 동일하지 않으면 종료 스테이터스가 1이 되므로 스크립트를 정지하게 됩니다.

diff from.txt to.txt > diff.txt

이렇듯 ‘예상한 실행 결과지만 종료 스테이터스가 0이 아님’ 같은 명령을 포함하는 스크립트에서 set -e를 쓰고 싶을 때는 몇 가지 방법이 있습니다.

  1. 일시적으로 set +e해서 set -e를 무효화하기

set 명령어에 +e 옵션을 사용하면 -e 지정이 무효화됩니다. 따라서 0이 아닌 값을 돌려주는 명령어 바로 앞에 +e, 명령어 다음에 -e를 지정하면 일시적으로 set -e를 무효화 할 수 있습니다.

  set +e
  diff from.txt to.txt > diff.txt
  set -e
  1. ||: 사용

set -e는 파이프라인의 마지막 스테이터스가 대상입니다. 따라서 앞의 명령어 결과가 0이 아닐 때 실행되도록 OR 연산자( || )를 사용하고 그 뒤에 :(널 명령어)을 실행하면 널 명령어는 언제나 0이 종료 스테이터스이므로 set -e에 걸리지 않습니다.

  diff from.txt to.txt > diff.txt ||:

여기서 널 명령어는 대신에 true 명령어를 쓸 수도 있습니다.

   

주의사항

  • set 명령어 -e 옵션을 사용하면 에러 시 스크립트가 종료되므로 스크립트 내부에 에러 처리를 작성하지 않는 코딩 스타일입니다. 하지만 ‘set -e를 금지하고 스스로 제대로 에러 처리를 해야 한다’라는 반대 주장도 있습니다. 목적과 상황에 따라 잘 선택하기 바랍니다.