정적 분석 (Static Analysis)

프로그램이 복잡해질수록, 코드 길이가 늘어날수록 정적 분석의 중요성은 커진다. 정적 분석은 런타임에 쉽게 발견할 수 없는 결함(defects)을 조기에 검출하여 더욱 정교하고 완성도 높은 코드를 생산하는 데 큰 도움이 된다.

C/C++ World에는 Coverity와 같은 값 비싼 기업용 도구도 있고, llvm과 같은 오픈 소스 범용 컴파일러에 포함되어 있기도 하다. Java는 findbug, 파이썬은 pep8을 지원하는 flake8, 루비는 RuboCop과 같은 도구들을 이용할 수 있다. 그렇다면 쉘 스크립트는 어떨까?

사실 쉘 스크립트를 만들면서 정적 분석을 해야겠다는 생각은 한 번도 해 본 적이 없다. 쉘 스크립트를 작성하는 목적은 대부분 실험 자동화, 결과 분석 등 이었기 때문에 해당 케이스에 의존적이었고, 한번 쓰고 버릴 코드라 생각했다. 그래서 대충대충 원하는 결과만 뽑을 수 있으면 되는 코드를 만들었다. 그런데 얼마 전 쉘 스크립트 정적 분석 도구를 발견했다. 사용해보니 신세계가 아닐 수 없었다.

그렇다. 오늘 팔아볼 약 소개할 도구는 쉘 스크립트 정적 분석기이다.

 

ShellCheck

깔끔한 쉘 스크립트를 만들고 싶으십니까?
최신 문법을 몰라 아재향 물씬한 스크립트를 만들고 계시다고요?
ShellCheck이 여러분의 코드를 아름답게 만들 수 있도록 도와드립니다.
지금 바로 설치하세요!

 
use_it

 
구구절절 설명하는 것 보다 써보면 좋다는 것을 확실히 알 수 있다. 다음 예제를 사용해 확인해 보자.

#!/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

 

사용해보니…

서버 진단 스크립트를 만들면서 처음 사용해보고 결과가 너무 만족스러웠다. 습관적으로 사용하던 레거시 코드들을 깔끔하게 고칠 수 있었고, 디버깅에 들어가는 시간과 노력을 대폭 줄일 수 있었다. 고수에게 코드 리뷰를 받은 기분이랄까?

만족감을 느끼며 커밋 메시지에 이렇게 새겼다.

do_shellcheck

 

ShellCheck을 써라! 두번 써라!