rcmdnk's blog

1日1問、半年以内に習得 シェル・ワンライナー160本ノック (Software Design plusシリーズ)

Gitのcommit時などに自動でlinterなどを動かしてコードを直したり警告を出したりしてくれる pre-commitでシェルスクリプトの文法チェッカーである ShellCheckを使う方法について。

pre-commit

pre-commitはPython製のGit hookを使ってcommit時などにlinter等を実行するツールです。

Python製ですが他の言語の文法チェックなども行えます。

pipで

$ pip install pre-commit

とインストールすることも出来ますし、 Homebrewで

$ brew install pre-commit

でもインストールできます。

ShellCheck

ShellCheck はシェルスクリプトの文法チェッカーで一番有名なツールだと思います。 (このツール自体はHaskell製。)

インストールはaptyumなどLinuxでのパッケージマネージャーを使っても出来ますし、Homebrewを使ってインストールすることも出来ます。 その他いろいろな方法でインストールできますし、 shellcheck.net でWeb上のエディタでチェックすることも出来ます。

加えて、下にも書きますが、Pythonのパッケージマネージャーのpipでもインストールすることが出来ます。

pre-commitのSupported hooksに載っているもの

公式のページにリストされているhooksを見てみると

Supported hooks

以下の3つがShellCheckを含んでいるようです。

jumanjihouse/pre-commit-hooks or syntaqx/git-hooks

下2つに関して見てみると、例えばjumanjihouse/pre-commit-hooksの方は .pre-commit-hooks.yaml を見てみると

1
2
3
4
5
6
7
8
- id: shellcheck
  name: Test shell scripts with shellcheck
  description: Shell scripts conform to shellcheck
  entry: hooks/shellcheck.sh
  language: script
  types: [shell]
  exclude_types: [csh, perl, python, ruby, tcsh, zsh]
  args: [-e, SC1091]

な感じになっていて、スクリプトを別途呼び出しています。 このスクリプトの中身は

1
2
3
4
5
6
7
8
9
10
11
readonly DEBUG=${DEBUG:-unset}
if [ "${DEBUG}" != unset ]; then
  set -x
fi

if ! command -v shellcheck >/dev/null 2>&1; then
  echo 'This check needs shellcheck from https://github.com/koalaman/shellcheck'
  exit 1
fi

shellcheck "$@"

な感じでshellcheckを呼び出しているだけです。

警告があるようにshellcheck自体は自分で入れる必要があります。

個人的な開発ではshellcheckを入れておけば良いのでこの辺を使うのもありですが、できればpre-commit installなどで必要なツールは勝手に入れてくれるようにしてもらいたいところ。

一方でpre-commitを自分で作ることはこれまでしたことありませんでしたが、これらのレポジトリのものはスクリプトを実行するだけのもので、それをいくつか用意して1つのパッケージ化したような感じです。

おそらくそれぞれ個人的な用途で作ったもので、中身も網羅的というよりはおそらくこの人達が必要だったんだろうな、という感じのものの集まりで、他にもこういったレポジトリがいくつかSupported hooksのページには載っています。

Suported hooksには自分で作ったものを載せたければPull Requestして載せてもらう感じで結構なんでも載せてくれる感じみたいです。

色々あるのでちょっとノイズなものもありますが、自分用にちょっとpre-commitを用意しようと思う場合にはこれらは参考になるかも。

shellcheck-py

shellcheck-py/shellcheck-py はこのレポジトリ自体はpre-commitのためのレポジトリ、の前にshellcheck-pyのPythonパケージレポジトリになっています。

このshellcheck-pyはpipで

$ pip shellcheck-py

でインストールでき、Pythonのインストール先のbinディレクトリにshellcheckコマンドがインストールされます。

Pythonで作り直したものかと思いきや

shellcheck-py/setup.py at main · shellcheck-py/shellcheck-py

を見てみるとshellcheckをダウンロードしてきて、そのバイナリファイルをそのままコピーしてインストールしているだけのようです。

こういう方法がありなのか、と言う感じですが、この方法を使えば原理的にあらゆるコマンドをpipでインストールできるようになります。 自分で使うところはあるかわからないですがちょっと覚えておきたいところ。

で、このレポジトリにはpre-commitから呼べるように .pre-commit-hooks.yamlが用意されていてlanguagepythonになっているので、このhookを自分の.pre-commimt-config.yaml

1
2
3
4
5
repos:
  - repo: https://github.com/shellcheck-py/shellcheck-py
    rev: v0.9.0.2
    hooks:
    -   id: shellcheck

みたいな感じで書いておけばpre-commit installでpre-commitの作る仮想環境の中にshellcheckコマンドがインストールされます。

なので現状ではこのhookが一番良いのではないかな、と思ってます。

shellcheck公式のpre-commit hook

上のSupported hooksには載っていませんがShellCheckの作者がpre-commit用のhookを作っています。

こちらはどうやっているかというと .pre-commit-hooks.yaml を見てみるとlanguagedocker_imageになっています。

shellcheckの入ったdockerイメージを使うので、これもshellcheckを別途インストールする必要はありません。 仮想環境にもインストールはされない状態です。

一方でdockerが使える状態になってないと使えません。

dockerが使える環境であれば一番クリーンな状態のまま使うことは出来ますが、 ちょっとした環境でやりたい時にdockerがないと走らせられないことになります。

直接自分でコマンドを走らせる

最初のスクリプトを走らせるだけのものがありましたが、 それらはshellcheckを自分でインストールしておく必要がありました。

もしshellcheckを別途インストールするのであれば、直接それを使うhookを作ってしまった方がシンプルかと思います。

1
2
3
4
5
6
7
8
9
repos:
  - repo: local
    hooks:
      - id: shellcheck
        name: shellcheck
        entry: shellcheck
        language: system
        types: [shell]
        require_serial: true

こんな感じのrepolocalにしたhooksshellcheckを作ってあげれば良いだけ。 typesなどのオプションはhooksが用意されているレポジトリを使う際に自分で指定することが無いのでちょっと調べる必要がありますが、 上記のshellcheckを含むレポジトリの .pre-commit-hooks.yamlを参考にしたり pre-commitのドキュメントを読んだりすればそれほど難しいものではないです。

まとめ

shellcheckをpre-commitで使う方法ですが、 shellcheck-py/shellcheck-py を使って、

1
2
3
4
5
repos:
  - repo: https://github.com/shellcheck-py/shellcheck-py
    rev: v0.9.0.2
    hooks:
    -   id: shellcheck

shellcheckコマンドもpre-commitによって管理してもらうようにするのが一番便利だと思いました。

shellcheckは必ず環境に入っている前提だ、という場合には

1
2
3
4
5
6
7
8
9
repos:
  - repo: local
    hooks:
      - id: shellcheck
        name: shellcheck
        entry: shellcheck
        language: system
        types: [shell]
        require_serial: true

として直接環境のshellcheckを使うようにしてあげるのもありかな、と思います。

Sponsored Links
Sponsored Links

« Pythonでdocstringの一部を継承する方法 GitHub ActionsでのGistへのアクセス制限の回避 »

}