Bashやシェルスクリプトは今でもCIジョブやサーバ、緊急の修正で現役です。得意な領域、より安全なスクリプトの書き方、他のツールを使うべきタイミングを学びましょう。

「シェルスクリプト」と言うと、多くの場合はコマンドラインシェル内で動く小さなプログラムを指します。シェルはあなたのコマンドを読み、他のプログラムを起動します。多くのLinuxサーバでは、そのシェルはPOSIX sh(標準化された最小限のベースライン)か、追加機能を持つ最も一般的な「sh互換」シェルであるBashです。
DevOpsの文脈では、シェルスクリプトはOSツール、クラウドCLI、ビルドツール、設定ファイルをつなぐ薄い接着剤の層です。
Linuxマシンにはすでにコアユーティリティ(grep, sed, awk, tar, curl, systemctl など)が備わっています。シェルスクリプトはこれらのツールを直接呼べるため、ランタイムやパッケージ、追加依存を増やさずに済みます。これは最小イメージ、リカバリシェル、制限された環境で特に有用です。
シェルスクリプトが光るのは、多くのツールが単純な契約(コントラクト)に従っているからです:
cmd1 | cmd2)。0は成功、非ゼロは失敗—自動化では重要です。本稿では Bash/シェルがDevOps自動化、CI/CD、コンテナ、トラブルシューティング、移植性、安全対策にどう適合するかに焦点を当てます。シェルを完全なアプリケーションフレームワークに変えることは目指しません—その必要がある場合は別の適切な選択肢を示します(それでもシェルが周辺で役立つ場面はあります)。
シェルスクリプトは単なる「レガシーな接着剤」ではありません。手動のコマンド列を再現可能なアクションに変える、小さく信頼できる層です。特にサーバや環境、ツールを素早く跨るときに役立ちます。
長期的にマネージドなインフラを目指していても、ホストの準備(パッケージのインストール、設定ファイルの配置、権限設定、ユーザ作成、シークレットの取得など)が必要な瞬間はよくあります。短いシェルスクリプトは、シェルとSSHがあればどこでも動くため、こうした一時的/稀なタスクに最適です。
多くのチームはルックブックをドキュメントで保管しますが、最も価値の高いルックブックは実行可能なスクリプトです:
ルックブックをスクリプト化すると人的ミスが減り、結果の一貫性が上がり、引き継ぎも容易になります。
インシデント発生時にはフルアプリやダッシュボードよりも明快な答えが欲しいものです。grep, sed, awk, jq といったツールを組み合わせたシェルパイプラインはログを切り分け、出力を比較し、ノード間のパターンを見つける最速手段の一つです。
日常業務では同じCLI手順をdev/staging/prodで繰り返すことが多い:アーティファクトのタグ付け、ファイル同期、ステータス確認、安全なロールアウトなど。シェルスクリプトはこれらのワークフローをキャプチャして環境間での一貫性を保ちます。
すべてがきれいに統合されているわけではありません。シェルは「ツールAがJSONを出す」→「ツールBが環境変数を期待する」といった場面で、呼び出しの仲介やチェック、リトライを追加して、新しい統合やプラグインを待たずに作業を進められます。
シェルスクリプトとTerraform、Ansible、Chef、Puppetのようなツールは関連する問題を解きますが、置き換え可能ではありません。
IaC/構成管理はシステム・オブ・レコードと考えてください:望ましい状態を定義し、レビューし、バージョン管理して一貫して適用する場所です。Terraformはインフラ(ネットワーク、ロードバランサー、DB)を宣言します。Ansible/Chef/Puppetはマシン構成と継続的な収束を記述します。
シェルスクリプトは通常グルーコードです:ステップやツール、環境をつなぐ薄い層。スクリプト自体が最終状態を“所有”しないことがありますが、操作を実用的にするために手順の調整を行います。
シェルは次のような用途でIaCの良い補助になります:
例:Terraformでリソースを作成するが、Bashスクリプトで入力を検証し、正しいバックエンドが設定されていることを確認してから terraform plan とポリシーチェックを実行し、apply を許可する、というパターン。
シェルは迅速に実装できて依存が少ないため、緊急の自動化や小さな調整タスクに最適です。一方で長期的なガバナンスは課題です:スクリプトが「ミニプラットフォーム」に肥大化し、一貫性のないパターン、弱い冪等性、監査の難しさを招くことがあります。
実用的なルール:状態管理や再現性が重要なものはIaC/構成ツールで扱い、その周辺の短く組み合わせ可能なワークフローはシェルで包む。スクリプトが事業上重要になったら、コアロジックをシステム・オブ・レコードに移し、シェルはラッパーとして残すと良いでしょう。
CI/CDシステムはステップをオーケストレートしますが、実際に“作業をする”何かが必要です。Bash(またはPOSIX sh)はほとんどのランナで利用可能で起動が容易、追加ランタイムなしにツールを繋げられるので、現在でもデフォルトのグルーです。
パイプラインでは多くの場合シェルステップが次のような地味だが重要な役割を担います:依存のインストール、ビルドの実行、成果物のパッケージングとアップロードなど。
典型例:
パイプラインは環境変数で設定を渡すので、シェルスクリプトはそれらの値を配布するルーターになります。安全なパターン:環境変数からシークレットを読み出し決して echo しない、ディスクに書き込まないようにすること。
推奨:
set +x を使ってトレースを無効にするCIは予測可能な挙動を求めます。良いパイプラインスクリプトは:
キャッシュや並列ステップの管理は通常CIシステム側の責任で、スクリプトは共有キャッシュを跨いで確実に管理することはできません。スクリプトはキャッシュキーやディレクトリを一貫させる役割を持てます。
チームで読みやすく保つために、スクリプトを小さな関数に分け、一貫した命名をし、短い使用方法ヘッダを付けて /ci/ のようにリポジトリ内に置いてレビューされるようにすると良いです。
もしチームが「もう一つのCIスクリプトが必要だ」と常に書いているなら、AI支援のワークフローはボイラープレート(引数解析、リトライ、安全なログ、ガードレール)作成を助けます。Koder.ai では、ジョブの説明を自然言語で与えるとスタータBash/sh スクリプトを生成し、実行前に計画モードで反復できます。Koder.aiはソースコードのエクスポートやスナップショットとロールバックをサポートするので、スクリプトを付け焼き刃の断片ではなくレビュー可能なアーティファクトとして扱いやすくなります。
多くのツールがまずCLIを提供するため、コンテナやクラウドのワークフローでもシェルは実用的な接着剤です。インフラが他で定義されていても、起動、検証、収集、復旧のための小さな確実な自動化が必要になります。
コンテナのエントリポイントでシェルをよく見かけます。小さなスクリプトは:
エントリポイントスクリプトは短く予測可能に保ち、セットアップの後は exec でメインプロセスを置き換えるようにしてください。
日常のKubernetes作業では軽量なヘルパーが役立ちます:kubectl のラッパーで現在のコンテキスト/ネームスペースを確認したり、複数ポッドからログを集めたり、インシデント時に最近のイベントをまとめるなど。スクリプトはプロダクションに向いている場合実行を拒否したり、ログをチケット用にバンドルしたりできます。
AWS/Azure/GCP CLIは一括タスク(リソースのタグ付け、シークレットのローテーション、インベントリのエクスポート、夜間の非本番停止など)に最適です。シェルはこれらのアクションを繋いで再現可能なコマンドにする最速手段であることが多いです。
壊れやすい解析と信頼性の低いAPIがよくある失敗原因です。可能なら構造化出力を使ってください:
--output json のようなJSON出力を使い、jq で解析する(人間向けテーブルをgrepするより堅牢)JSON + jq と基本的なリトライロジックの小さな変更で、「自分のラップトップでは動く」スクリプトを何度でも信頼して実行できる自動化に変えられます。
何かが壊れたとき、新しいツールチェーンよりも数分で答えが欲しいことが多いです。シェルはホストにすでに存在し、素早く動き、小さな信頼できるコマンドを繋いで現状を明確に示せるため、インシデント対応に最適です。
障害時は通常、いくつかの基本を検証します:
df -h, df -i)free -m, vmstat 1 5, uptime)ss -lntp, ps aux | grep ...)getent hosts name, dig +short name)curl -fsS -m 2 -w '%{http_code} %{time_total}\n' URL)シェルスクリプトはこれらを標準化して複数ホストで一貫して実行でき、結果をインシデントチャネルにそのまま貼れる形式にできます。
良いインシデントスクリプトはスナップショットを収集します:タイムスタンプ、ホスト名、カーネルバージョン、最近のログ、現在の接続、リソース使用状況。この「状態バンドル」は火消し後の根本原因解析に役立ちます。
#!/usr/bin/env bash
set -euo pipefail
out="incident_$(hostname)_$(date -u +%Y%m%dT%H%M%SZ).log"
{
date -u
hostname
uname -a
df -h
free -m
ss -lntp
journalctl -n 200 --no-pager 2>/dev/null || true
} | tee "$out"
インシデント自動化はまず読み取り専用にするべきです。修復アクションは明示的にして確認プロンプト(または --yes フラグ)を要求し、何が変わるかを明確に出力してください。こうすることで対応者は速く動けても、二次的な事故を起こしにくくなります。
実行が「ランナーが持っているもの」に依存する場合(Alpine/BusyBox、異なるディストリ、CIイメージ、開発者のmacOSなど)、移植性は重要です。最大の問題は、すべてのマシンに同じシェルがあると仮定することです。
POSIX sh は最低共通分母です:基本的な変数、case、for、if、パイプライン、単純な関数を含みます。どこでも動くスクリプトを目指すときに選びます。
Bash は配列、[[ ... ]] テスト、プロセス置換(<(...))、set -o pipefail、拡張グロブ、より便利な文字列操作などの機能を持ちます。これらの機能は自動化を速めますが、/bin/sh がBashでない環境では壊れます。
sh をターゲットにする(Alpineのash、Debianのdash、BusyBoxなど)。macOSはデフォルトで古いBash(3.2)を持っていることがあるので、Bashスクリプトでもバージョン差で壊れる点に注意してください。
よくあるbashism:[[ ... ]]、配列、source(POSIXでは . を使う)、echo -e の挙動差など。POSIXを意図するなら実際のPOSIXシェル(dashやBusyBox sh)で書いてテストしましょう。
意図を示すシェバンを使ってください:
#!/bin/sh
または
#!/usr/bin/env bash
そしてリポジトリに「Bash ≥ 4.0が必要」などの要件を書いておくと、CIやコンテナ、チームの整合が取りやすくなります。
CIで shellcheck を走らせるとbashism、引用ミス、安全でないパターンを検出できます。/blog/shellcheck-in-ci のような内部ガイドにチームを誘導すると良いでしょう。
シェルスクリプトは本番システムや資格情報、機密ログにアクセスすることが多いです。いくつかの防御的な習慣で「便利な自動化」を「事故を起こさない自動化」に変えられます。
多くのチームはスクリプト冒頭に:
set -euo pipefail
を置きます。
-e はエラーで停止しますが、if 条件や while テスト、一部のパイプラインでは意図しない副作用を生むことがあります。失敗が想定される箇所は明示的に扱ってください。-u は未設定変数をエラーにします—タイプミス検出に有効です。pipefail はパイプライン中の失敗を反映させます。意図的に失敗を許すなら command || true のように明示してください。できればエラーを確認して適切に処理します。
クオートされていない変数は単語分割やワイルドカード展開を招きます:
rm -rf $TARGET # 危険
rm -rf -- "$TARGET" # より安全
変数は特に分割が必要な場合を除き常に引用してください。引数を組み立てる場合はBashの配列を使うと安全です。
evalの回避、最小特権パラメータ、環境変数、ファイル名、コマンド出力は信頼しない前提で扱います。
eval やシェルコード文字列の生成は避けるsudo を使うのではなく、1コマンドだけに限定するecho、デバッグトレース、冗長なcurlログを避ける)set -x は機密コマンドの周りで無効にする一時ファイルは mktemp を使い、trap でクリーンアップします:
tmp="$(mktemp)"
trap 'rm -f "$tmp"' EXIT
また -- でオプション解析を終了させ(例:rm -- "$file")、機密データを書き込むファイル作成時は制限的な umask を設定してください。
スクリプトは「一時の対処」から「本番運用」へと変わりがちです。保守性はそれを破綻させないために重要です。
少しの構造化が大きな効果を生みます:
scripts/ や ops/ にまとめて発見しやすくするbackup-db.sh, rotate-logs.sh, release-tag.sh)を心がけるスクリプト内部は小さな単一責務の関数と一貫したログ出力を使って可読性を上げます。log_info / log_warn / log_error のような単純なパターンは雑多な echo を減らします。
また -h/--help をサポートすると、チームが安心してスクリプトを実行できます。
シェルはテストが難しいわけではなく、むしろ省略されがちです。まずは軽量に始めます:
--dry-run のような安全モードでのスモークテストテストは引数/出力、終了ステータス、ログ行、副作用(ファイル作成やコマンド呼び出し)に注力してください。
次の2つがレビュー前に問題をキャッチします:
CIで両方を実行し、標準が誰かの記憶に依存しないようにしてください。
運用スクリプトもアプリコードと同様にバージョン管理し、コードレビューを行い、変更管理に紐づけるべきです。変更はPRで行い、振る舞いの変更はコミットメッセージに記録し、複数のリポジトリやチームで使われる場合は単純なバージョンタグを付けることを検討してください。
信頼できるインフラスクリプトは予測可能で安全に再実行でき、プレッシャー下でも読み解けることが重要です。次のパターンが「自分の環境だけで動く」から「チームで信頼される」へ導きます。
スクリプトは二度実行される前提で書く(人、cron、リトライされるCIジョブ)。「アクションをする」より「状態を確保する」を優先します。
mkdir -p を使ってディレクトリを作る(mkdir ではない)望ましい終状態が既にあるなら、余計な作業をせず正常終了するのがルールです。
ネットワークは壊れます。レジストリはレート制限をかけます。APIはタイムアウトします。失敗しやすい操作はリトライと増分遅延でラップします。
retry() {
n=0; max=5; delay=1
while :; do
"$@" && break
n=$((n+1))
[ "$n" -ge "$max" ] && return 1
sleep "$delay"; delay=$((delay*2))
done
}
自動化ではHTTPステータスをデータとして扱います。curl -fsS(非2xxで失敗、エラーを表示)を使い、必要ならステータスを取得して判定します。
resp=$(curl -sS -w "\n%{http_code}" -H "Authorization: Bearer $TOKEN" "$URL")
body=${resp%$'\n'*}; code=${resp##*$'\n'}
[ "$code" = "200" ] || { echo "API failed: $code" >&2; exit 1; }
JSONを扱うなら grep の脆弱なパイプより jq を使ってください。
同じ資源をめぐってスクリプトが競合するのはよくある障害原因です。flock が使えるなら使い、なければPIDチェック付きのロックファイルを実装してください。
ログは明瞭に(日付、主要アクション)しつつ、機械読み取り用モード(JSON)を提供するとダッシュボードやCI集約に役立ちます。小さな --json フラグは有用です。
シェルはコマンドを繋ぎ、ファイルを動かし、既存ツールを調整するのに優れますが、すべての自動化に最適とは限りません。
スクリプトが小さなアプリケーションのように見えてきたら移行を考えます:
if、一時フラグ、特例だらけ)API統合、JSON/YAML操作、ユニットテストや再利用可能なモジュールが必要ならPythonが有力候補です。堅牢なエラーハンドリング、豊富なライブラリ、構造化された設定管理が簡単にできます。
Goは頒布可能なツール(単一の静的バイナリ)、予測可能なパフォーマンス、型安全性を求める場面に適しています。最小ランタイムのコンテナや制限されたホストで実行したい内部CLIツールに向いています。
実用的なパターンは、シェルを薄いラッパーとして残し、本当のロジックは別の言語に委ねる方法です:
この流れはKoder.aiのようなプラットフォームとも相性が良く、最初は薄いBashラッパーでプロトタイプを作り、ロジックが成熟したらソースをエクスポートして通常のリポジトリ/CIへ移す、という進化が可能です。
シェルを選ぶ:コマンドのオーケストレーションが主で、短命で、ターミナルでテストしやすい場合。
別言語を選ぶ:ライブラリ、多構造データ、クロスプラットフォーム、テスト可能で成長するコードが必要な場合。
Bash学習は道具箱として捉えると良いです。一度に全部を習得しようとせず、週に使う20%に注力し、痛みを感じたら機能を追加していきます。
最初は自動化を予測可能にするルールとコアコマンドを押さえます:
ls, find, grep, sed, awk, tar, curl, jq(jqはシェルではありませんが必須)|, >, >>, 2>, 2>&1, here-strings$?、set -e のトレードオフ、明示的なチェック(cmd || exit 1)"$var"、配列、単語分割での落とし穴foo() { ... }, $1, $@, デフォルト値大きな「アプリ」を作るより、ツールを繋ぐ小さなスクリプトを書くことを目標にしてください。
毎週短いプロジェクトを一つ選び、最初から端末で実行できる状態に保ちます:
各スクリプトは最初は100行以下に抑え、長くなったら関数に分割してください。
ランダムなスニペットより公式や一次情報を使いましょう:
man bash, help set, man test簡単なスターターテンプレを作り、レビュー用チェックリストを用意します:
set -euo pipefail(またはドキュメント化した代替)trap でのクリーンアップシェルスクリプトは、ビルドの実行、システムの検査、依存の少ない管理タスクといった「速くてポータブルな接着剤」として最も効果を発揮します。いくつかの安全なデフォルト(引用、入力検証、リトライ、Lint)を標準化すれば、シェルは信頼できる自動化スタックの一部になります。スクリプトを「プロダクト」に昇格させたい場合は、Koder.aiのようなツールがその進化を支援し、ソース管理、レビュー、ロールバックを維持したまま運用していけます。
DevOpsでのシェルスクリプトは通常「グルーコード」です。つまり既存のツール(Linuxユーティリティ、クラウドCLI、CIステップ)をパイプ、終了コード、環境変数でつなぐ小さなプログラムです。
シェルはサーバやランナー上ですでに利用可能で依存が少ないため、すばやく実行できる軽量な自動化に最適です。
POSIX sh は、BusyBox/Alpineのような多様な環境(最小コンテナ、未知のCIランナーなど)で動かす必要がある場合に使います。
Bash は、実行環境を管理できる(自分のCIイメージや運用ホストなど)か、[[ ... ]]、配列、pipefail、プロセス置換などのBash固有の機能が必要な場合に選びます。
目的を明確にするために、シェバンでインタープリタを固定し(例: #!/bin/sh または #!/usr/bin/env bash)、必要なバージョンをドキュメントに記載してください。
ほとんどのLinuxイメージはシェルとコアユーティリティ(grep、sed、awk、tar、curl、systemctlなど)を備えています。
そのためシェルは次の用途に向いています:
IaC/構成管理ツールは通常、望ましい状態を定義しレビューしバージョン管理するシステム・オブ・レコードです(ネットワーク、ロードバランサー、DBなどを宣言)。シェルスクリプトは手順やツール、環境をつなぐグルーコードとして使うのが自然です。
シェルがIaCを補完する例:
CIでのベストプラクティス:予測可能かつ安全にすること。
set +x でトレースを無効にするjq でJSONを解析するネットワークやAPIが不安定なステップはバックオフ付きリトライを入れ、最大試行後は明確に失敗させてください。
エントリポイントは短く決定論的に保ちます:
セットアップが終わったら exec でメインプロセスを実行し、シグナルと終了コードが正しく伝播するようにしてください。
長時間のバックグラウンドプロセスは推奨されません(適切な監視戦略がない限り)。
よくある問題点:
/bin/sh が dash(Debian/Ubuntu)や BusyBox sh(Alpine)であり、Bashではないecho -e、sed -i、テスト構文の差異移植性が必要なら、ターゲットシェル(/BusyBox)でテストし、CIでShellCheckを使って“bashisms”を検出してください。
堅牢な基本は次のとおりです:
set -euo pipefail
習慣として:
速く一貫した診断のために、少数のコマンドを標準化してタイムスタンプ付きで出力を収集します。
典型的なチェック:
ほとんどのチームにとって次の2つのツールで十分です:
テストは軽量でOK:
--dry-run つきのスモークテストdash"$var"(単語分割/グロブを防ぐ)evalや文字列でのシェル構築は避ける-- を使う(例:rm -- "$file")mktemp と trap で安全に一時ファイルを作成・削除するset -e は便利ですが、期待される失敗は明示的に扱ってください(cmd || true や明確なチェック)。
df -h, df -iuptime, free -m, vmstat 1 5ss -lntpjournalctl -n 200 --no-pagercurl -fsS -m 2 URLまずは“読み取り専用”のチェックにして、修正アクションは明示(プロンプトまたは --yes)にしてください。
スクリプトは scripts/ や ops/ に置き、最小限の --help を備えると運用が楽になります。