
大量なシェルスクリプトの開発・運用で悩んでいるあなたへ贈る話
はじめに
こんにちは、くるふとです。
ナビタイムジャパンでは、時刻表 API や地図描画 API の 開発・運用業務を主に担当しています。
皆さんは業務でシェルスクリプトを扱ったことがあるでしょうか?
多かれ少なかれ、エンジニアの業務でシェルスクリプトを見かけることはあると思います。
ファイルを操作するスクリプトやサーバーの状態をチェックするスクリプトなど、さまざまな場面でシェルスクリプトが利用されているはずです。
多くのシェルスクリプトを開発・運用している人であれば、それらの悩みや辛みを感じることは少なくないでしょう。今使っているシェルスクリプトを別のプログラミング言語で書き直したいと感じた人もいるかと思われます。今回の記事はそういった方に向けた記事となります。
シェルスクリプトの開発・運用で出てくる悩み
シェルスクリプトを扱う業務に携わっている方なら、以下のような悩みを持ったことがあると思います。
可読性が低い
シェルはスクリプト言語としての性質が強いため、書くのは簡単ですがコードの可読性を担保するのが難しいです。
条件分岐が多くなると処理を追っていくのが辛くなってきます。
いわゆるシェル芸とかも、読みにくいコードができてしまう原因になり得ます。
エラーの調査が難しい
業務で利用しているシェルスクリプトが突然エラーを吐くようになった経験、一度はあるかと思います。
シェルはエラーハンドリングの実装が難しく、エラーが起きた際どのコマンドで失敗したかを追うのが難しい場面が多々あります。
なんなら、エラーログを吐くことなくただただ黙ってスクリプトが止まることすらもあります。
変更が難しい
突然動かなくなったシェルスクリプトのエラー調査をするのは大変ですが、それを直す作業の方がもっと大変です。数年にわたって利用されてきたスクリプトの細かな仕様を知っている人は少なく、どこをどう直せば動くようになるのか全くわからないことすらあります。
この悩みはどうすれば解消されるのか?
こうした悩みを抱えている人なら、こう思ったことがあるでしょう
別のプログラミング言語で書き直したい…!
Python などであればより柔軟性のある言語仕様、便利な標準ライブラリを利用することができます。
パフォーマンスが求められる内容であれば Go や Rust なども選択肢に入ります。
その他の言語も魅力的な点が多く、シェルスクリプトをこれらの言語に書き換えたいという気持ちはよく分かります。
もし、そのシェルスクリプトが2つ3つとかであれば、別言語への書き換えに挑戦することは良いことだと思います。しかし、これが中〜大規模のシステムで稼働している、大量のシェルスクリプト群であればどうでしょうか?
たしかに、今扱っているシェルスクリプトを全て書き直せば、保守性が向上し今より効率良く業務をこなせる可能性は高いです。しかし、実際に移行をするために必要なコストは高く、移行の決断をすることは簡単ではないはずです。通常の開発・運用業務をこなしながらスクリプトを移行していくのは、工数的にも精神的にも負担は重いです。仮に移行を進めたとしても、途中で移行が頓挫してしまうリスクもあります。
別のプログラミング言語に書きおなしたいという気持ちとどう向き合うか?
では、このような悩みに対してどう向き合うのがよいのでしょうか?
「まずは現状の構成で頑張ってみる」が私の考えです。大量のスクリプトを別言語に書き換える行為はコストが高く、いきなりの決断は難しいです。であれば、まずは現状の構成での解決を図るのが、コストも最小限で済むはずです。
シェルスクリプトの状態のままでも保守性を上げることは可能です。先の項目で出た悩みに対してそれぞれどのようなアプローチができるか、より具体的に解説していきます。
可読性の向上
実は、シェルスクリプトには Linter と Formatter が存在します。shellcheck と shfmt になります。
これらは CLI としてインストールできるため、ローカルや CI での実行が可能です。
さらに、VSCode のプラグインも存在します。 VScode 上でシェルスクリプトのフォーマットや構文チェックが可能です。
shellcheck と shfmt のより詳細な解説は弊社の過去記事が存在します。こちらも当記事と合わせて読んでみてください。
また、シェルスクリプトにはスタイルガイドも存在します。
Google が提供する Shell Style Guide です。
スタイルガイドをチームに導入することによって一貫性のあるコードが書けるようになります。
Shell Style Guide についても弊社の過去記事があります。こちらもあわせて読んでみてください。
エラーの調査方法
bash には実行時にトレースを表示するオプションが存在します。 bash -x でシェルスクリプトを実行することで、スクリプト内の変数に代入されている値や関数の実行結果を確認することができます。
$ cat ./main.sh
#!/bin/bash
set -euo pipefail
function main() {
local message="Hello World!"
echo "message: ${message}"
}
main "$@"
$ bash -x ./main.sh
+ set -euo pipefail
+ main
+ local 'message=Hello World!'
+ echo 'message: Hello World!'
message: Hello World!
シェルスクリプト内に set -x コマンドを入れることで同じようにトレースを表示させることもできます。
$ cat ./main.sh
#!/bin/bash
set -euox pipefail # set -x を付与
function main() {
local message="Hello World!"
echo "message: ${message}"
}
main "$@"
$ ./main.sh
+ main
+ local 'message=Hello World!'
+ echo 'message: Hello World!'
message: Hello World!
また、VSCode であればエディタ上で bash スクリプトのデバッグをすることができます。
まず最初に、 Bash Debug プラグインをインストールします。
インストールが完了したらコマンドパレットから > Debug: Add Configuration, Bash Debug の順に選択します。


すると launch.json という設定ファイルが作成されます。

例えば、エディタ上の main.sh を実行したい場合は下記のような設定に書き換えます。
{
"version": "0.2.0",
"configurations": [
{
"type": "bashdb",
"request": "launch",
"name": "sample bash script",
"program": "${workspaceFolder}/main.sh"
}
]
}
書き換えた後にコマンドパレットで > Debug: Start Debugging を実行するとスクリプトのデバッグを開始することができます。

デバッグツールのより詳しい使い方は公式ドキュメントが参考になります。
修正性の向上
システムによっては、サーバー上に配置されているシェルスクリプトを直接書き換える形の運用が存在する場合もあります。
シェルスクリプトに限った話ではないですが、コードはリポジトリで管理をし、自動でリポジトリ内のスクリプトがサーバーに転送されるようにするのが望ましいです。
リポジトリ管理による自動化には以下のメリットがあります。
変更内容を複数人でレビューできる
自動化によりオペレーションミスが減る
ロールバックが容易になる
また、 bash にはテストフレームワークがいくつか存在します。もし改修頻度が高いスクリプトなのであれば、こういったテストフレームワークを導入するのも手となります。今回は2つのテストフレームワークを紹介します。
bats
bats は @test "print hello message" { … } というフォーマットでテストケースを作成できます。スクリプトの出力結果やファイル操作結果をテストしたい場合に便利です。
インストール方法
使い方
$ cat main.sh # テスト対象のスクリプト
#!/bin/bash
set -euo pipefail
function main() {
echo "Hello World!"
}
main "$@"
$ cat bats_sample.bats # bats スクリプト
#!/usr/bin/env bats
@test "print hello message" {
msg="$(./main.sh)"
[[ "${msg}" == "Hello World!" ]]
}
$ ./bats_sample.bats # bats を用いたテストの実行
bats_sample.bats
✓ print hello message
1 test, 0 failures
ShellSpec
Shellspec は独自の DSL を用いてシェルスクリプトのテストを実行することができます。構文や機能が充実しており、表現の幅が広いテストフレームワークとなっています。
インストール
使い方
$ shellspec --init # shellspec 実行に必要な設定ファイルとヘルパースクリプトを作成
create .shellspec
create spec/spec_helper.sh
$ cat ./main.sh # テスト対象のスクリプト(引数の数値を自乗する)
#!/bin/bash
set -euo pipefail
function main() {
local i="$1"
echo "$((i ** 2))"
}
main "$@"
$ cat ./spec/spec_sample.sh # テストケースとなるスクリプト
Describe 'main.sh'
It 'squared test: 2^2'
When run ./main.sh 2
The output should equal '4'
End
It 'squared test: 9^2'
When run ./main.sh 9
The output should equal '81'
End
It 'squared test: 15^2'
When run ./main.sh 15
The output should equal '225'
End
End
$ shellspec # テスト実行
Running: /bin/sh [bash 3.2.57(1)-release]
Finished in 0.14 seconds (user 0.11 seconds, sys 0.02 seconds)
0 examples, 0 failures
まとめ
いかがでしたでしょうか?
今回のようなスクリプトの書き換えや、中〜大規模のシステムの移行はどうしても工数の見積もりが大きくなってしまいます。最初に「全部作り直そう!」となってしまうとどうしてもその後の動きが鈍化してしまいがちです。
なので、まずは「現状の構成でやってみる」という選択肢を持つと、業務の幅が広がってくると思います。
今回紹介した手法は、いずれの手段も導入のハードルは低く、且つ一定以上の効果が見込めるもののはずです。当記事の内容がみなさんの業務内容の改善に繋げれば幸いです。