Bash 잘 쓰기

주의: 이 글은 컴퓨터 세계의 세 종교(운영체제, 프로그래밍 언어, 에디터) 중 하나에 대해 이야기한다. 이들의 선택은 어디까지나 선호의 문제이며 이곳에 적힌 내 생각은 많은 오류를 포함한다.

나는 Bash를 종종 쓴다. 어떤 이들은 Bash가 사용해서는 안되는 구시대의 유물처럼 묘사를 하지만 내 생각은 다르다. Bash는 오직 쉘을 위한 언어인지라 종종 다른 스크립트 언어들보다 더 깔끔하고 직관적인 표현을 가능케 한다.

이 글에서는 언제, 어떻게 쉘 스크립트를 쓰는 것이 좋은지 개인적인 생각을 정리해 보고자 한다.


언제?

쉘 스크립트는 여러 쉘 명령들을 하나로 묶을 때 쓴다. 다시 한 번 강조하면 쉘 명령들, 그리고 그것들을 하나로 묶을 때.

그러려면,

  1. 쉘 명령들에 익숙해야 한다. cd, ls, cp, mv, rm, tar, pushd, popd, grep, xargs, scp, rsync, head, tail, cat 등을 잘 모른다면 쉘 스크립트 프로그래밍으로부터 얻는 이득이 크지 않을 것이다. 반면 쉘 명령들에 어느 정도 익숙하다면 시도해 볼 만 하다.

  2. 하고자 하는 일이 쉘 명령들만으로 구성될 수 있어야 한다. 쉘 명령들만으로 할 수 있는 일이 무엇이 있을까? 특정 디렉토리로 이동한다거나, 여러 파일들의 이름을 일괄적으로 바꾼다거나, 실행 가능한 바이너리 파일을 실행하는 것 뿐일 것이다. 쉘 명령어만으로 피보나치 수열을 계산하거나 하는 건 쉽지 않을 것이다.

    "에이, 별 거 아니네."

    이런 생각이 들 것이다. 그렇다. 쉘 스크립트는 그렇게 별 거 아닌 주제에 반복되는 작업들을 하나로 묶을 때 쓰면 되는 것이다. 조금이라도 더 복잡한 논리적 연산이 필요할 때엔 더 높은 수준의 언어를 쓰는 것이 좋겠다.

어떻게?

파이프라인

쉘을 써 본 사람이라면 |, >, >> 등 파이프라인을 본 적이 있을 것이다. 다른 여느 언어보다 프로세스 사이의 텍스트 통신이 간결하여 이걸 잘 쓰는 것이 좋다. 뭐 다들 아는 거니까 넘어가자.

set 명령어

set은 쉘의 옵션을 설정하는 명령어이다. 메뉴얼을 보면 많은 옵션이 있지만 대부분 뭔 말인지 모르겠고 내가 쓰는 것만 공유한다.

위의 두 옵션은 항상 키는 편이다. 잘못된 쉘 스크립트로 인해 시스템이 엉망이 되는 걸 막을 수 있다.

쉘 스크립트의 도입부에 다음과 같이 적으면 된다.

set -e -u -x

임시 파일 + exit 콜백 등록

보통의 리눅스 시스템에서는 mktemp라는 명령어로 /tmp 디렉토리에 이름이 겹치지 않는 임시 파일을 만들 수 있는데 주로 파일을 다루는 쉘 스크립트에서는 이를 요긴하게 쓴다. 예를 들면 아래와 같이 .tar.gz 확장자를 가지는 임시 파일을 만들 수 있다. 두 번째 줄은 쉘 스크립트가 exit할 때 생성한 임시 파일을 지우도록 콜백을 등록하는 것이다.

TMP=$(mktemp --suffix=.tar.gz)
trap "rm ${TMP}" EXIT

trap을 이용하면 아래와 같이 쉘 스크립트 실행 중 다른 디렉토리로 이동한 후 쉘 스크립트가 exit할 때 자연스럽게 원래의 디렉토리로 돌아오도록 할 수도 있다.

pushd .
trap "popd" EXIT
cd /home

(추가 2018년 1월 21일)

여러 콜백 명령문 등록 + 파이프라인 관련 이슈

여러 명령어를 콜백으로 추가하는 일은 생각보다 복잡하다. 예를 들어 trap "cmd1" EXITtrap "cmd2" EXIT로 두 명령어를 콜백으로 등록하면 나중에 등록된 cmd2만이 EXIT 이벤트 발생 시 실행된다. 그럼 어떻게 해야 될까? 아래처럼 콜백 등록할 명령어들을 배열에 모았다가 EXIT 때 하나씩 실행하면 된다.

trap_cmd=()
function run_trap_cmd {
    tlen=${#trap_cmd[@]}
    for (( i=0; i<${tlen}; i++ )); do
        ${trap_cmd[$i]}
    done
}
trap run_trap_cmd EXIT

function add_trap_exit {
    trap_cmd+=("$1")
}

add_trap_exit "cmd1"
add_trap_exit "cmd2"

한 가지 더 주의할 점은 콜백 등록할 명령어에 파이프라인(| 이나 >)이 포함되어 있으면 실행될 때 이들이 Bash의 문법이 아닌 단순 문자열로서 해석된다는 것이다. 이를 피하기 위해 명령어들을 함수로 정의하고 함수 이름을 등록하는 방법을 사용할 수 있다.


솔직히 Bash는 구시대 유물이 맞다. 조심해야 할 것이 산더미다. 마치 C처럼. 최근에 Bash Pitfalls라는 글을 읽었는데 Bash는 정말 '위험한' 것이었다.

그래도 Bash를 조심조심 잘 쓰자. 그러면 종종 간결한 쉘 스크립트가 우리를 편하게 할 것이다.

2016-08-24 씀.