배쉬 스크립트에서 지뢰 피하기
뭐 이딴게 다있어
태그: dev
모름지기 프로그래머는 귀찮은 반복적인 일을 자동화할 줄 알아야 한다. 대부분의 자동화는 현존 최강의 스크립트 언어 파이썬으로 해결되지만, 리눅스 커맨드를 요리조리 지지고 볶고 해야하는 경우는 쉘 스크립트가 더 편한 경우가 종종 있어서 자주 쓰는 편이다.
하지만 쉘 스크립트, 특히 배쉬 스크립트는 … 언어 모델이 지뢰밭이라서 까딱 잘못 하면 밟고 터지기 십상이다. 오늘은 내가 이때까지 밟은 지뢰들을 정리한다.
지뢰밭 목록
대입문에서 공백이 의미를 해친다
말 그대로 어떤 변수에 값을 대입할 때, 무심코 다른 언어에서 하던
것처럼 lhs = rhs
로 쓰는 순간 에러코드 127을 볼 수 있다. 이
뜻은 “현재 PATH
에서 님이 실행한 커맨드 못찾겠음” 인데, 즉 저
대입문을 대입문이 아니라 커맨드의 실행으로 인식한
것이다. 그러고보니 커맨드에서 옵션 줄 때 종종 =
를 쓰지 않던가?
암튼 원하는 걸 얻으려면 딱 붙여 써야 한다: lhs=rhs
조건문에서는 공백이 의미를 갖는다
일단 쉘에서 조건문은 다음처럼 쓴다.
if [ COND ]; then
...
fi
if
로 열고 fi
로 닫는 참신한 발상은 대체 누구 생각인지 궁금해지지만
넘어가자. 뱀발로 그러면 당연히 case
문은 esac
으로 닫겠지?
그렇다. 그럼 for
문은 rof
으로 닫지 않을까? 아니다. 이건
함정이다. 반복문은 do
와 done
으로 닫는다. 놀랍지 않은가?
삼천포로 빠졌다. 다시 돌아와서, 저기서 [ ... ];
부분이 조건문의
조건을 작성하는 부분인데, 여기서 중요한 것은 중괄호 사이에 반드시
공백이 있어야 한다는 것이다. 예를 들어서 VAR
변수가 정수 1과
같은지를 체크하려면:
if [ $VAR -eq 1]; then
...
fi
이렇게 쓰면 파싱에러가 뜬다. 정확하게
if [ $VAR -eq 1 ]; then
...
fi
이렇게 써줘야한다. 아주 독불장군이다.
파이프라이닝에는 숨겨진 지뢰가 있다.
이게 무슨 뜻인지 결론부터 말하면 파이프라이닝은 서브 쉘에서 실행된다. 이게 대체 뭔 말이냐? 다음 상황을 보자.
빌드 대상을 확인하는 함수와 실제 빌드를 수행하는 함수를 쉘로 대충
다음과 같이 작성했다고 하자. 이때 validate
함수는 빌드 대상을
BUILD_TARGETS
배열 변수에 쌓고 full_build
함수에서 이걸 읽어서
빌드를 수행하려고 한다.
function validate
{
...
BUILD_TARGETS=( )
...
BUILD_TARGETS=( "${BUILD_TARGETS[@]}" "${BUILD_TARGET}" )
...
}
function full_build
{
real_build_command --targets "${BUILD_TARGETS[@]}"
}
타입도 없고 공백이 시맨틱을 갖는 쉘에서 배열을 쓸려는 시도 자체가 잘못된 것 같긴 하지만(…) 넘어가자. 암튼 이 함수들을 이렇게 쓰면 잘 된다:
...
validate
full_build
근데 빌드 하면서 stdout
에 찍히는 로그를 타임스탬프랑 함께 파일에 찍고
싶어서 다음처럼 바꿨다고 하자.
validate
full_build | ts | tee full-build.log
그럼 바로 에러가 뜬다!
그 이유는 바로 쉘에서 파이프라이닝되는 명령들은 서브 쉘에서
실행되어 독립적인 환경을 갖기 때문이다. 그래서 full_build
가 그냥
호출되면 validate
에서 설정한 빌드 대상 변수를 잘 읽어오지만,
파이프라이닝과 함께 실행되는 경우 BUILD_TARGETS
변수가 정의되지
않았고 , 그래서 실패가 뜬다. 세상에 뭐 이딴 경우가. 스펙을 모르면
100% 밟고 터질 수 밖에 없는 초특급 지뢰다.
그래서 예전 끈닷넷님이 공유해주셨던 배쉬의 함정을 다시 읽어보고 있는데 여기 8번에 딱 이 상황이 나와있더라.
Changes to
count
won’t propagate outside thewhile
loop because each command in a pipeline is executed in a separate SubShell. This surprises almost every Bash beginner at some point.
호환마마보다도 무서운 언어다. 이 외에도 위의 “배쉬 함정” 글에는 정말
참신하고도 기발한 지뢰들이 많으니 꼭 읽어보자. 그리고 shellcheck
를
생활화하도록 하자. (물론 이것도 완벽한 솔루션은 아니다. 특히 정규식
관련해서는 의미가 달라지는 제안을 하기도 하던데 이건 다음 기회에.)
지뢰밭을 피하려면 지뢰탐지기가 필요하듯 우리에겐 쉘을 잘 쓰기 위한
장비가 필요하다. 물론 제일 좋은 것은 쉘을 쓰지 않아도 되는 것이겠지만
리눅스가 점령한 세상에서 그런게 있을리가 없잖아? 결국 각개로 살아남는
수 밖에 없으니 조심 또 조심하자.