VSCode 上でシェルスクリプトのまずい書き方を検出してみよう (with ShellCheck on Docker)

UNIX/Linux での作業の効率化に欠かせないシェルスクリプトですが、手軽に書ける反面、様々な罠があることでも知られています。

そんな罠にはまらないよう、シェルスクリプトの解析ツール「ShellCheck」を使ってチェックしましょう、というお話です。

ShellCheck とは?

静的解析によりシェルスクリプトの問題点を分析し、より良い書き方を提案してくれる CLI の Linter ツールです。

構文の問題点やバグだけでなく、推奨されない書き方も教えてくれます。

シェルスクリプトの初心者・上級者に関わらず、入れておくだけで安心してスクリプトを書けるようになります。

ShellCheck がチェックしてくれること

ShellCheck がチェックしてくれる内容は とても多岐に渡ります。例えば…

  • クォーティング処理に関する指摘:
    • ダブルクォーテーションで囲わずに変数展開している場合など。
  • 条件判定の書き方に関する指摘:
    • 意図しない判定ミスが起きそうな書き方など。
  • 初心者がやりがちなミスの指摘:
    • 変数定義のイコールの前後にスペースがある場合など。
  • 危険な書き方の指摘:
    • 破壊的な rm 実行など。
  • ポータビリティ(実行環境に依存しない書き方)に関する指摘:
    • 一行目のシバンに #!/bin/sh と書いてるのに bash 依存のコマンドを書いている場合など。

これだけでも心強いと思いますが、上記はほんの一例です。

ShellCheck を入れてみよう

この記事では Visual Studio CodeShellCheck 連携プラグイン により ShellCheck を呼び出し、コーディングの際にリアルタイムにチェックできる環境の作り方を紹介します。

尚、ShellCheck の導入は Docker イメージとして提供されているものを使います。そのため事前に Docker のインストール が必要です。

なぜ ShellCheck を直接インストールせずに Docker イメージを使うの?

ShellCheck は brew や yum や apt-get などのパッケージ管理ツールでシステムに直接インストールすることもできます。

しかし、環境によってビルドにめちゃくちゃ時間がかかります。macOS では1時間ほど要することもあるようです。Docker イメージならビルドなしですぐ使えます。

もし時間がかかってもシステムに直接インストールしたい場合は 公式のインストール方法 を参照してみてください。

VSCode に ShellCheck を導入する手順

1. ShellCheck の Docker イメージを取得する

docker pull koalaman/shellcheck:v0.7.1

これだけでコマンドラインで ShellCheck を使えるようになります。

例えば、/path/to/myscript.sh に置かれているスクリプトをチェックしたい場合は以下のように実行します。

cd /path/to
docker run --rm -v "$PWD:/mnt" koalaman/shellcheck:v0.7.1 myscript.sh

2. VSCode から実行するためのキッカースクリプトを作る

VSCode 上で shellcheck を使うには docker run コマンドなしで実行できるようにする必要があります。

そのため、公式で説明されているように以下のようなキッカースクリプトを用意しておきます。

#!/bin/bash

exec docker run --rm -i -v "$PWD:/mnt:ro" \
  koalaman/shellcheck:v0.7.1 "$@"

作成したスクリプトは chmod +x コマンドで実行権限のパーミッションを付与しておきます。スクリプトはどこに置いてもいいですが、ファイルパスは控えておいてください。

3. VSCode に拡張機能をインストールする

次に VSCode に ShellCheck を動かすための拡張機能「vscode-shellcheck」をインストールします。

インストールできたら VSCode の設定を開き、shellcheck.executablePath の設定に先ほど作成したキッカースクリプトのパスをフルパスで指定します。(システムに ShellCheck をインストールした場合はこの設定は不要です。)

VSCode 上の設定例

以上で導入完了です。

適当にシェルスクリプトを書いてリアルタイムにチェックされるか確認してみる

下の図は適当にシェルスクリプトを書いてみたときの様子です。

ShellCheck により問題点が指摘されている様子

何やら4行目に波下線が出ています。これは検出された問題のあるコードの箇所を示しています。ウィンドウ下部の PROBLEMS の欄に、具体的な問題の内容が表示されています。

Double quote to prevent globbing and word splitting. shellcheck(SC2086)

一般的に変数を使うときは二重引用符 “” で囲うべき とされているので、その指摘となります。(囲わないと意図しない単語分割やグロブ展開が起きる危険性があります。)

メッセージの末尾にある SC2086 というのは ShellCheck の各チェック事項に対して割り当てられたコード番号で、このコード番号で調べると問題の詳細を知ることができます。例えば SC2086 の Wiki ページ を見ると直し方のコード例と共に、なぜこの書き方が問題なのか、解説を確認することができます。めちゃ親切。

まとめ

今回は二重引用符で囲うべきという指摘がされる例を挙げましたが、この他にも様々なことをチェックしてくれます。実際に ShellCheck を入れて書いてみるとその安心感が分かるかと思います。

ShellCheck が指摘してくれた問題点を直していくだけでベストプラクティスとされる書き方を反映したスクリプトを書くことができますし、上達にも繋がるのでおすすめです。

関連記事

シェルスクリプトで指定した範囲の数値 (range) を取得する

seq コマンドを使うと簡単にできます。以下のコマンドで 1 から 5 までの数字を改行区切りで得られます。

#!/bin/bash
seq 1 5

seq コマンドの結果を用いてループする場合は以下のようになります。

#!/bin/bash

# for 文の場合
end=5
for i in $(seq 1 $end); do
  echo $i
done

# while 文の場合 - サブシェルパターン
end=5
seq 1 $end | while read -r i; do
  echo $i
done

# while 文の場合 - プロセス置換パターン
end=5
while read -r i; do
  echo $i
done < <(seq 1 $end)

ちなみにループしたいだけであれば、Bash のみなりますが二重丸括弧 (( )) を用いて for 文で書くこともできます。

#!/bin/bash
end=5
for ((i = 1; i <= $end; i++)); do
  echo $i
done

他には Ruby の Range オブジェクトの作り方に近い記法もあり、以下のようになります。結果は同じですが、この方法だと範囲として指定している数値部分を変数にすることはできません。

#!/bin/bash
for i in {1..5}; do
  echo $i
done