정적 분석 (Static Analysis)
프로그램이 복잡해질수록, 코드 길이가 늘어날수록 정적 분석의 중요성은 커진다. 정적 분석은 런타임에 쉽게 발견할 수 없는 결함(defects)을 조기에 검출하여 더욱 정교하고 완성도 높은 코드를 생산하는 데 큰 도움이 된다.
C/C++ World에는 Coverity와 같은 값 비싼 기업용 도구도 있고, llvm과 같은 오픈 소스 범용 컴파일러에 포함되어 있기도 하다. Java는 findbug, 파이썬은 pep8을 지원하는 flake8, 루비는 RuboCop과 같은 도구들을 이용할 수 있다. 그렇다면 쉘 스크립트는 어떨까?
사실 쉘 스크립트를 만들면서 정적 분석을 해야겠다는 생각은 한 번도 해 본 적이 없다. 쉘 스크립트를 작성하는 목적은 대부분 실험 자동화, 결과 분석 등 이었기 때문에 해당 케이스에 의존적이었고, 한번 쓰고 버릴 코드라 생각했다. 그래서 대충대충 원하는 결과만 뽑을 수 있으면 되는 코드를 만들었다. 그런데 얼마 전 쉘 스크립트 정적 분석 도구를 발견했다. 사용해보니 신세계가 아닐 수 없었다.
그렇다. 오늘 팔아볼 약 소개할 도구는 쉘 스크립트 정적 분석기이다.
ShellCheck
깔끔한 쉘 스크립트를 만들고 싶으십니까? 최신 문법을 몰라 아재향 물씬한 스크립트를 만들고 계시다고요? ShellCheck이 여러분의 코드를 아름답게 만들 수 있도록 도와드립니다. 지금 바로 설치하세요!
구구절절 설명하는 것 보다 써보면 좋다는 것을 확실히 알 수 있다. 다음 예제를 사용해 확인해 보자.
#!/bin/bash
T0=`date +%s`
sleep 1
T1=`date +%s`
ELAPSED_TIME=$((T1-T0))
echo "START_TIME: " ${T0}
echo "END_TIME: " ${T1}
echo "ELAPSED_TIME: ${ELAPSES_TIME} sec"
ShellCheck의 웹페이지는 Ace(Ajax Cloud Editor)를 제공하고 있다. 이 에디터에서 스크립트를 작성하거나 이미 작성한 스크립트를 붙여넣으면 즉시 피드백을 준다.
에디터에 예제 코드를 붙여넣자 위 그림과 같이 2건의 결함과 2건의 제안(각각 2건씩 중복)이 출력되었다. 하나하나 살펴보면 다음과 같다.
SC2006: Use $(..) instead of legacy `..`.
# 레거시 스타일이다. 아재 코드라 할 수 있다.
# `...`와 같이 backtick(또는 backquote)로 감싼 명령은
# 중세이전 Bourne Shell에서 사용하던 형식이다.
# Bash에서는 $( ... ) 형태로 변경되었다.
# 가능하면 최신 문법을 사용하지 않겠는가!
# Bash가 나온지도 거의 30년이 지나 이제 최신이라 하기 어렵지만
# Bourne Shell 보다는 최신이다.
SC2034: ELAPSED_TIME appears unused. Verify it or export it.
# 변수 ELAPSED_TIME은 선언만하고 사용되지 않는다.
# 확인해보고 필요 없다면 제거하자.
SC2086: Double quote to prevent globbing and word splitting.
# 변수를 사용할 때, 쌍따옴표로 감싸주는 것을 추천한다.
# "${VAR}" 이렇게 하면 글로빙 또는 단어가 분리되는 문제를 막을 수 있다.
SC2153: Possible misspelling: ELAPSES_TIME may not be assigned, but ELAPSED_TIME is.
# 오타가 있는 것 같다.
# 여기서 사용한 ELAPSES_TIME이라는 변수는 할당되어 있지 않다.
# 아마도 앞에서 선언만하고 사용하지 않은 ELAPSED_TIME인 것 같다.
이처럼 레거시 코드도 지적해주고, 오타도 잡아주고, 런타임에 발생할지도 모르는 문제에 대해 경고도 해준다. ShellCheck을 쓰기만 하면 완벽한 스크립트 코드를 만들 수 있을 것 같은 기분이 든다. 어서, 지금, 당장 스크립트를 만들고 싶다.
그런데 불편한 점이 있다. 스크립트를 만들 때마다 ShellCheck 웹페이지에 접속하고, 쉘 스크립트를 붙여넣고, 검사 결과를 반영하고, 다시 붙여넣어 확인하자니 너무 번거롭다. 웹 브라우저가 아닌 터미널에서 사용하고 싶다. 그럼 로컬 PC에 설치해야 하는데…
터미널에서 사용
ShellCheck은 대부분의 패키지 관리자를 지원하는 패키지들을 배포하고 있다. OS의 패키지 매니저에 따라 골라 설치하시라.
- On Debian / Ubuntu
# apt-get install shellcheck
- On REHL / CentOS
# yum -y install epel-release # yum install ShellCheck
- On Fedora
# dnf install ShellCheck
- On macOS
$ brew install shellcheck
설치 후 앞에서 사용했던 예제를 파일로 저장하고 터미널에서 검사하면 웹페이지와 동일한 결과를 얻을 수 있다.
$ shellcheck test.sh
In test.sh line 2:
T0=`date +%s`
^-- SC2006: Use $(..) instead of legacy `..`.
In test.sh line 4:
T1=`date +%s`
^-- SC2006: Use $(..) instead of legacy `..`.
In test.sh line 5:
ELAPSED_TIME=$((T1-T0))
^-- SC2034: ELAPSED_TIME appears unused. Verify it or export it.
In test.sh line 7:
echo "START_TIME: " ${T0}
^-- SC2086: Double quote to prevent globbing and word splitting.
In test.sh line 8:
echo "END_TIME: " ${T1}
^-- SC2086: Double quote to prevent globbing and word splitting.
In test.sh line 9:
echo "ELAPSED_TIME: ${ELAPSES_TIME} sec"
^-- SC2153: Possible misspelling: ELAPSES_TIME may not be assigned, but ELAPSED_TIME is.
한결 편리해졌다. 그러나 개발자는 게으른 짐승. 에디터에서 바로 확인할 수 있으면 더 편리할 것 같다. 그래서 플러그인을 검색해보았다.
플러그인
vim에 syntastic이라는 플러그인을 설치하면, 파일로 저장할 때 ShellCheck이 동작하여 정적 분석을 해준다. 설치 과정은 다음과 같다.
- syntastic 설치를 쉽게 하기 위해 pathogen 설치
$ mkdir -p ~/.vim/autoload ~/.vim/bundle && curl -LSso ~/.vim/autoload/pathogen.vim https://tpo.pe/pathogen.vim
- vim이 pathogen을 실행하도록
~/.vimrc
에 다음과 같은 명령 추가execute pathogen#infect()
- syntastic 설치
$ cd ~/.vim/bundle && git clone --depth=1 https://github.com/vim-syntastic/syntastic.git
이제 vim에서 ShellCheck을 사용할 준비가 끝났다. 앞의 예제를 입력하고 저장해보자.
위와 같이 라인 왼쪽에 결함(>>
) 또는 제안(S>
)이 있음을 표시해준다. 해당 라인으로 커서를 이동하면 하단에 에러 메시지가 출력된다. 바로 수정해 반영하고 피드백을 받아 볼 수 있다.
아래 표와 같이 vim 뿐만 아니라 많이 사용되는 에디터들을 위한 플러그인이 제공되고 있다. 익숙한 도구용 플러그인을 골라 사용하면 된다.
에디터 | 플러그인 |
---|---|
vim | syntastic |
emacs | flycheck |
sublime | SublimeLinter |
atom | linter |
사용해보니…
서버 진단 스크립트를 만들면서 처음 사용해보고 결과가 너무 만족스러웠다. 습관적으로 사용하던 레거시 코드들을 깔끔하게 고칠 수 있었고, 디버깅에 들어가는 시간과 노력을 대폭 줄일 수 있었다. 고수에게 코드 리뷰를 받은 기분이랄까?
만족감을 느끼며 커밋 메시지에 이렇게 새겼다.