シェルスクリプトを書くときにいつもやるやつを調べた
bash のシェルスクリプトを書くときに、いつも脳死で以下をやっている。(同僚が整備してくれたものをコピペしている)
- エディタなり CI で shellcheck をまわす
set -euxo pipefail
と冒頭に書く- こんな感じ
#!/bin/bash
set -euxo pipefail
いつまでもコピペではさすがにアレなので、意味を調べたメモ。
shellcheck
koalaman/shellcheck: ShellCheck, a static analysis tool for shell scripts
- イケてない書き方に警告を出してくれる
- それぞれの警告にはエラーコード割り振られていてとても便利
- エラーコードごとに正誤例、解説が書かれているのでわかりやすい
- CI もそうだし、エディタのプラグインも充実 しているのでとりあえず入れておくと良い
set
set
という builtin でフラグを立てることによってスクリプトの挙動を変更できる。
設定の仕方によって、エラー時の abort や間違いやすい挙動を防ぐことで、バグに気づかずなんとなく動いてしまう状況を防げる。以下の記事では上手く設定したものを strict mode のようなものだと紹介していた。
-e
- コマンドが失敗した時点でスクリプト全体を即座にエラー終了する
- これがないとエラーがあっても以降の処理を継続してしまう
- スクリプト全体の exit code が最後のコマンドのものになってしまい、CI との相性も悪い
# -e なし
$ cat test.sh
set -e
grep abc foo # 存在しないファイルを grep しようとしてエラー
echo 1
$ bash test.sh
grep: foo: No such file or directory
1
$ echo $?
0 # exit code が 0 になってしまっている
# -e あり
$ cat test.sh
set -e
grep abc foo
echo 1
$ bash test.sh
grep: foo: No such file or directory
$ echo $?
2
-u
- 初期化していない変数があるとエラーにしてくれる
- 例は前述の記事より引用
# -u なし
$ cat test.sh
firstName="Aaron"
fullName="$firstname Maxwell" # `n` を小文字にタイポ
echo "$fullName"
$ bash test.sh
Maxwell
# -u あり
$ cat test.sh
set -u
firstName="Aaron"
fullName="$firstname Maxwell"
echo "$fullName"
$ bash test.sh
test.sh: line 3: firstname: unbound variable # エラーでタイポに気づくことができた
-x
- 実行するコマンドを出力してくれる
- 何をしたらどうなったかがログに残る
- CI に乗せるようなスクリプトなら入れたほうが良いし、ちょっとした処理でもデバッグしやすい
# -x なし
$ cat test.sh
echo 123
date
$ bash test.sh
123
Fri Feb 12 13:36:43 UTC 2021
# -x あり
$ cat test.sh
set -x
echo 123
date
$ bash test.sh
+ echo 123
123
+ date
Fri Feb 12 13:37:04 UTC 2021
-o pipefail
- パイプの途中でエラーがあれば exit code がそれになる
- デフォルトではパイプ最後のコマンドの exit code
# -o pipefail なし
$ set +o pipefail
$ grep foo bar | sort
grep: bar: No such file or directory
$ echo $?
0 # 標準エラーにはエラーメッセージが出たが、exit code は 0
# -o pipefail あり
$ set -o pipefail
$ grep foo bar | sort
grep: bar: No such file or directory
$ echo $?
2
IFS
Bash Strict Mode によると IFS=$'\n\t'
という設定もおすすめらしい。
IFS = Internal Field Separator
で、区切り文字の指定- デフォルトは
IFS=$' \n\t'
スペース、改行、タブ- 例えば
"a b c"
という文字列を for でまわすとa
b
c
の 3 要素に分割される
- 例えば
$ cat test.sh
items="a b c"
for x in $items; do
echo "$x"
done
$ bash test.sh
a
b
c
- スペース区切りが意図しない挙動になりがちなので、間違えの元らしい
- 次のような引数のパースをするような際には、確かに空白区切りはバクの温床になる
# IFS デフォルト
$ cat test.sh
for arg in $@; do
echo "doing something with file: $arg"
done
$ bash test.sh argFoo argBar 'some file.txt'
doing something with file: argFoo
doing something with file: argBar
doing something with file: some
doing something with file: file.txt
# IFS 改行・タブのみ
$ cat test.sh
IFS=$'\n\t'
for arg in $@; do
echo "doing something with file: $arg"
done
$ bash test.sh argFoo argBar 'some file.txt'
doing something with file: argFoo
doing something with file: argBar
doing something with file: some file.txt
参考
- Bash Strict Mode
- Writing Safe Shell Scripts
- Bash Reference Manual
- set builtin のリファレンスは 4.3.1節
- koalaman/shellcheck: ShellCheck, a static analysis tool for shell scripts
PR
山森 丈範 (著) 形式: Kindle版