ヒトリ歩き

愚痴とかいろいろ書きます

シェルスクリプトの静的解析をやってみよう

シェルスクリプトのスタイルガイドについて前回、書きました。

kotapontan.hatenablog.com

スタイルガイドを見ていたときに、ShellCheckというリンクがあったので、リンク先を見てみたところ、
シェルスクリプトの静的解析をするツールであることを知りました。
ということで、シェルスクリプトの静的解析ツールのShellCheckを使ってみたいと思います。
(前回、シェルスクリプトの静的解析ツールはないと書いちゃいましたが、ありましたね・・・w)

ShellCheckとは

github.com

ShellCheckの目的について、以下のことが書かれています。
(英語がスーパー苦手なので、誤訳してるかもしれません・・・)

  • 初心者による構文エラーを指摘し、明確にする。
  • シェルを直感で分からない、変な振る舞いで引き起こる中級レベルの問題を指摘し、明確にする。
  • 微妙な警告や、コーナーケース、落とし穴を指摘する。

初級者による構文エラーから上級者でも見つけることが難しい稀なエラーに関しても検出することが可能と思われます。

ShellCheckをインストールしてみる

ShellCheckをインストールします。
インストール方法は、yumでもインストールできるようですが、今回はwgetで書庫ファイルを取得し、動作環境で解凍することにしました。

$ scversion="latest"
$ wget -qO- "https://storage.googleapis.com/shellcheck/shellcheck-${scversion?}.linux.x86_64.tar.xz" | tar -xJv
$ shellcheck --version
ShellCheck - shell script analysis tool
version: 0.7.0
license: GNU General Public License, version 3
website: https://www.shellcheck.net

ShellCheckを実行してみる

実行環境は、次の通りです。

OS CentOS Linux release 7.5.1804 (Core)
Bash version 4.2.46(2)-release (x86_64-redhat-linux-gnu)

サンプルコードはこちらを用意しました。
パラメータにKey=Value形式で渡したValueの値を返却するスクリプトです。

#!/bin/bash

function getvalue() {

  declare -a array
  array+=( $@ )

  for value in ${array[@]}
  do
    echo ${value} | awk -F= '{ print $2 }'
  done
}

function main() {

  getvalue "$@"

}

main "$@"

ShellCheckを実行した結果はこちらになります。

$ ./shellcheck ~/getvalue.sh 

In /home/vagrant/getvalue.sh line 6:
  array+=( $@ )
          ^-- SC2206: Quote to prevent word splitting/globbing, or split robustly with mapfile or read -a.


In /home/vagrant/getvalue.sh line 8:
  for value in ${array[@]}
              ^---------^ SC2068: Double quote array expansions to avoid re-splitting elements.


In /home/vagrant/getvalue.sh line 10:
    echo ${value} | awk -F= '{ print $2 }'
          ^------^ SC2086: Double quote to prevent globbing and word splitting.

Did you mean: 
    echo "${value}" | awk -F= '{ print $2 }'

For more information:
  https://www.shellcheck.net/wiki/SC2068 -- Double quote array expansions to ...
  https://www.shellcheck.net/wiki/SC2206 -- Quote to prevent word splitting/g...
  https://www.shellcheck.net/wiki/SC2086 -- Double quote to prevent globbing ...

オプションなしで実行してみると、問題箇所の行と問題内容が標準出力で出力されます。
違反内容の情報は、wiki(https://www.shellcheck.net/wiki)に載っているので、出力されている違反番号(SCXXXX)で調べれば違反詳細と正しい記述に関する情報が書かれています。
では、サンプルのエラーの詳細を確認してみます。

変数はダブルクォートで囲む

変数をダブルクォートで囲むと、個々の要素のグロビングや単語分割を防ぐことができます。
サンプルスクリプトのパラメータに"aaa=bb b"と渡した場合、"bb b"の値が出力されるべきですが、ダブルクォートで囲んでいないため、単語が分割され、"bb"が出力されることになります。

静的解析結果通りに修正する

#!/bin/bash

function getvalue() {

  declare -a array
  array+=( "$@" )

  for value in "${array[@]}"
  do
    echo "${value}" | awk -F= '{ print $2 }'
  done
}

function main() {

  getvalue "$@"

}

main "$@"

ShellCheckを再実行すると違反はなくなり、何も出力されなくなりました。
静的解析で一部の違反番号だけを有効にしたい場合は、-i(--include)を指定することで、特定の違反番号だけを有効にすることができます。
また、一部の違反番号を除外したい場合は、-e(--exclude)を指定することで、特定の違反番号だけを除外することができます。

Jenkinsに静的解析結果を表示してみる

JenkinsのパイプラインからShellCheckを実行して、静的解析結果を出力してみます。
ShellCheckの-fオプションで、checkstyleを指定するとcheckstyle形式で静的解析結果を出力してくれます。
出力された静的解析結果は、warnings-ng-plugin を使って表示します。

ここで、注意すべき点があります。
ShellCheckは、静的解析で違反があると終了コードが0以外となるので、パイプラインでShellCheckコマンドを実行するとパイプラインが異常終了します。
そのため、ShellCheckをラップしたラッパースクリプトから実行し、ラッパースクリプトを正常終了させる必要があります。
パイプラインの定義は以下のように設定して実行しました。

pipeline {
    agent any
    stages {
        stage('exec') {
            steps {
                sh "/tmp/hoge.sh"
        
            }
        }
    }
    post {
        success {
             recordIssues(tools: [checkStyle(pattern: 'checkstyle-result.xml')])
        }
    }
}

パイプラインを実行し、静的解析結果が表示されることを確認できました。
f:id:kotapontan:20200227225621p:plain

まとめ

簡単な構文ミスから上級レベルの構文エラーまでチェックしてくれる印象を持ちました。
shの-nオプションではカバーできない範囲を大きくカバーしてくれるので、ShellCheckを実行し、問題箇所を修正した方が、確実に品質を上げることが出来ます。
Jenkinsでも静的解析結果を見ることができるので、プロジェクトにも取り入れていきたいです。