shellcheck と shfmt を使ってシェルスクリプトをキレイにしてみる
2020-12-24シェルスクリプトをキレイにするためのツールとして shellcheck と shfmt というものの存在を教えてもらったので、先日作った ACM の Certificate 発行スクリプトに対してそれらを実行し、キレイにしていきたいと思います。
目次
概要
shellcheck と shfmt というツールを使って、シェルスクリプトをキレイにします。ここで言う キレイに とは、文法上の問題をなくしたり、想定外の実行結果にならないようにしたり、見た目を整えたりすることを指します。
今回使う shellcheck と sh というツールについても簡単に紹介しておきます。
shellcheck
shellcheck (koalaman/shellcheck
) は、対象のシェルスクリプトに対して文法上の問題や想定外な実行結果が起きないように、問題点を指摘してくれるツールです。指摘してくれるポイントとしては、 GitHub の README に次のように書かれています。
- To point out and clarify typical beginner’s syntax issues that cause a shell to give cryptic error messages.
- To point out and clarify typical intermediate level semantic problems that cause a shell to behave strangely and counter-intuitively.
- To point out subtle caveats, corner cases and pitfalls that may cause an advanced user’s otherwise working script to fail under future circumstances.
ざっくり翻訳すると
- わかりにくいエラーメッセージを出すような初歩的な記述の問題点
- 想定と異なる動作をするような、中級レベルの問題点
- 将来的に動作しなくなるような、またあまり起こらないような微妙なケース、落とし穴などの上級レベルの問題点
について指摘してくれます。
クライアントにインストールして使用することもできますし、 Web 上ではシェルスクリプトを貼り付けるだけでチェックしてくれます。
- ShellCheck – shell script analysis tool
- koalaman/shellcheck: ShellCheck, a static analysis tool for shell scripts
shfmt
shfmt は mvdan/sh
リポジトリに含まれるツールで、対象のシェルスクリプトをフォーマットしてくれる Go 製のツールです。
shellcheck で文法チェック
では、早速 shellcheck で文法チェックしてみます。
今回はローカルマシン (macOS) にインストールして実行してみます。
インストール
brew install
でインストールできます。
$ brew install shellcheck
.
.
.
🍺 /usr/local/Cellar/shellcheck/0.7.1: 7 files, 9.0MB
$ shellcheck -V
ShellCheck - shell script analysis tool
version: 0.7.1
license: GNU General Public License, version 3
website: https://www.shellcheck.net
実行
以前作成した ACM の Certificate を発行するスクリプト
を issue-acm-certificate.sh
という名前で保存しておいて、そのファイルに対して shellcheck で文法チェックをかけます。
$ shellcheck issue-acm-certificate.sh
In issue-acm-certificate.sh line 5:
if [ $# != 3 ] || [ $1 = "" ] || [ $2 = "" ] || [ $3 = "" ]; then
^-- SC2086: Double quote to prevent globbing and word splitting.
^-- SC2086: Double quote to prevent globbing and word splitting.
^-- SC2086: Double quote to prevent globbing and word splitting.
Did you mean:
if [ $# != 3 ] || [ "$1" = "" ] || [ "$2" = "" ] || [ "$3" = "" ]; then
In issue-acm-certificate.sh line 24:
--domain-name ${TARGET_DOMAIN} \
^--------------^ SC2086: Double quote to prevent globbing and word splitting.
Did you mean:
--domain-name "${TARGET_DOMAIN}" \
In issue-acm-certificate.sh line 26:
--region ${REGION} \
^-------^ SC2086: Double quote to prevent globbing and word splitting.
Did you mean:
--region "${REGION}" \
In issue-acm-certificate.sh line 36:
--certificate-arn ${CERT_ARN} \
^---------^ SC2086: Double quote to prevent globbing and word splitting.
Did you mean:
--certificate-arn "${CERT_ARN}" \
In issue-acm-certificate.sh line 38:
--region ${REGION} \
^-------^ SC2086: Double quote to prevent globbing and word splitting.
Did you mean:
--region "${REGION}" \
In issue-acm-certificate.sh line 44:
--certificate-arn ${CERT_ARN} \
^---------^ SC2086: Double quote to prevent globbing and word splitting.
Did you mean:
--certificate-arn "${CERT_ARN}" \
In issue-acm-certificate.sh line 46:
--region ${REGION} \
^-------^ SC2086: Double quote to prevent globbing and word splitting.
Did you mean:
--region "${REGION}" \
In issue-acm-certificate.sh line 56:
if [ $VALIDATION_RECORD_NAME == $NONE ] || [ $VALIDATION_RECORD_VALUE == $NONE ] || [ $HOSTED_DOMAIN == $NONE ]; then
^---------------------^ SC2086: Double quote to prevent globbing and word splitting.
^----------------------^ SC2086: Double quote to prevent globbing and word splitting.
^------------^ SC2086: Double quote to prevent globbing and word splitting.
Did you mean:
if [ "$VALIDATION_RECORD_NAME" == $NONE ] || [ "$VALIDATION_RECORD_VALUE" == $NONE ] || [ "$HOSTED_DOMAIN" == $NONE ]; then
In issue-acm-certificate.sh line 63:
--hosted-zone-id ${HOSTED_ZONE_ID} \
^---------------^ SC2086: Double quote to prevent globbing and word splitting.
Did you mean:
--hosted-zone-id "${HOSTED_ZONE_ID}" \
In issue-acm-certificate.sh line 82:
if [ $? == 0 ]; then
^-- SC2181: Check exit code directly with e.g. 'if mycmd;', not indirectly with $?.
For more information:
https://www.shellcheck.net/wiki/SC2086 -- Double quote to prevent globbing ...
https://www.shellcheck.net/wiki/SC2181 -- Check exit code directly with e.g...
…いっぱい出ましたね。と言ってもほぼ同じ内容に関する指摘のようです。
SC2086: Double quote to prevent globbing and word splitting.
まずこの指摘は、 glob や文字列の分割を防ぐためにダブルクオーテーションで囲みましょう というものです。例えば、下記のスクリプトは S3 バケットに Cache-Control 付きのオブジェクトを Put するスクリプトです。
CACHE_CONTROL="public, max-age=1209600"
aws s3api put-object \
--bucket sample-bucket \
--key sample.txt \
--body ./sample.txt \
--cache-control $CACHE_CONTROL
このシェルスクリプト (s3-put-object.sh
) を実行すると、下記のようにエラーとなります。
$ sh s3-put-object.sh
usage: aws [options] <command> <subcommand> [<subcommand> ...] [parameters]
To see help text, you can run:
aws help
aws <command> help
aws <command> <subcommand> help
Unknown options: max-age=1209600
これは、変数 CACHE_CONTROL
がそのまま展開されて、 AWS CLI の s3api put-object
コマンドのオプション内で分割された文字列が指定されたことによるエラーです。このようなことにならないためにも、変数をダブルクオーテーションで囲みましょうという指摘です。
shellcheck には各指摘に関する詳細ページが用意されているので、そちらで内容及び修正方法を確認することができます。
SC2086 · koalaman/shellcheck Wiki
SC2181: Check exit code directly with e.g. ‘if mycmd;’, not indirectly with $?.
これは、スクリプトの終了コードは $?
を使わずに直接チェックしましょうという指摘です。詳細ページでは $? = 0
で判定するのが冗長だと書かれています。
この指摘に対する対応としては、今回はコマンドを関数にして、その関数の実行を判定することにしてみます。例えば、 Certificate をリクエストする部分を次のように書き換えます。
request_certificate () {
# request certificate
echo "Request certificate for '${TARGET_DOMAIN}' to ACM."
CERT_ARN=$( \
aws acm request-certificate \
--domain-name "${TARGET_DOMAIN}" \
--validation-method DNS \
--region "${REGION}" \
--output text) \
&& sleep 5 \
&& echo -e "\t CERT_ARN = ${CERT_ARN}"
}
if ! request_certificate; then
echo "Failed to request certificate."
exit 1
fi
SC2181 · koalaman/shellcheck Wiki
shfmt でフォーマット
続いて、 shfmt を使ってシェルスクリプトをフォーマットしてみます。
インストール
README に従って、下記のコマンドでインストールします。
$ GO111MODULE=on go get mvdan.cc/sh/v3/cmd/shfmt
$ shfmt -version
v3.2.1
実行
色々オプションがありますが、今回は フォーマットを行ったファイルのファイル名を出力する -l
と、フォーマット結果を標準出力ではなくファイルに書き出す -w
を指定しています。それ以外のオプションについては shfmt -h
とかで実行すれば確認できます。
$ shfmt -l -w issue-acm-certificate.sh
issue-acm-certificate.sh
確認してみると、インデントとか諸々調整されててキレイにフォーマットされていました。
まとめ
shellcheck と shfmt というツールを使って、シェルスクリプトをキレイにした話でした。
最終的には下記のようなシェルスクリプトになったので、過去との差分については Revisions を参照してください。
comments powered by Disqus