name: writing-bash description: Rules for writing bash scripts and shell functions. when_to_use: TRIGGER when editing or creating a .sh file, or when writing a shell script / bash function / multi-line shell pipeline. SKIP for Python, Ruby, fish, or other non-bash scripts.
Bash Writing Rules
Rules
パイプを避けられる時は避ける。
grep regex | awk '{ do-something }'→awk '/regex/ { do-something }'echo "$var" | cmd→cmd <<< "$var"(here-string; サブシェルを生成しない)
プロセス生成はなるべく避ける。
- jq 式の中で現在時刻が必要なら
date +%sを spawn せず jq 組み込みのnowを使う cp src dst && chmod 0755 dst→install -m 0755 src dst(1 プロセスで完結)var="$(cat)"/var="$(cat file)"は原則禁止(cat を spawn)。ファイルはvar="$(< file)"、通常の stdin はvar="$(</dev/stdin)"(bash 組み込み・fork なし)- 例外: hook の stdin は socket。
$(</dev/stdin)は/proc/self/fd/0を開き直すため socket では 0 byte になる(Claude Code は hook payload を socket 渡し)。既に開いている fd 0 を直読するvar="$(cat)"(またはIFS= read -r -d '' var)を使う
- 例外: hook の stdin は socket。
- slurp した変数を jq 等へ渡すときは、再び cat せず here-string
cmd <<<"$var"(またはリダイレクト)で渡す - 同じ入力から複数値を取るとき jq を複数回呼ばない。1 回の
jq -r '.a, .b'(改行区切り出力)を{ read -r a; read -r b; }で受ける
- jq 式の中で現在時刻が必要なら
実行されるスクリプトには exec bit を必ず立てる。 shebang 付きで bare command(
./x/ hook のcommandfield /$PATH経由)として起動されるファイルは mode 644 だとPermission deniedで exec 不能。- 作成したら
chmod +x。 git 管理下ならgit ls-files -s <path>が100755か確認(100644は実行不可) cp/cp -r/install(-m無し)は source の mode をそのまま deploy 先へ複製する。 deploy script が-mで上書きしない限り、 source の mode 誤りは deploy 先の exec 失敗に直結する
- 作成したら
プログラム名をハードコードしない。
- コマンドパスを絶対パスで書かず PATH に任せる(
/usr/bin/jq→jq) - スクリプト自身の名前はリテラルではなく
$(basename "$0")を使う
- コマンドパスを絶対パスで書かず PATH に任せる(
core-utils 以外のコマンドは存在確認してから使う。
command -v foo >/dev/null 2>&1 || { echo "foo not found"; exit 1; }あるいは任意実行ならif command -v foo >/dev/null 2>&1; then foo ...; fishellcheck が使えるなら使う。 スクリプトを書いたら
shellcheck --enable=deprecate-which script.shで検証する(deprecate-which=SC2230 を上乗せ:whichは Debian trixie で削除・非推奨ゆえ存在確認はcommand -vを使う)。 設定ファイルは置かず CLI 引数で運用。shellcheck の抑制(適用判定は
writing-code「指摘は修正が基本、 抑制は例外」): 対象行の直前に# shellcheck disable=SC2086(code 指定)を置く。 shebang 直後に置くと file 全体に効く。 誤検知が確実 / 逆に読みにくくなる / 正当な理由がある時のみ、 理由を添えて使うobsolete commands を使わない(代替を使う)。 未保守ツールは bug fix が来ず、 新しめ distro / 最小 image では存在しないことがある。 スクリプトでは保守された標準の代替を使う:
避ける 代替 理由 whichcommand -v非 POSIX・Debian trixie で削除(shellcheck SC2230 が検出) egrepgrep -Edeprecated fgrepgrep -Fdeprecated ifconfigip addrnet-tools 未保守 → iproute2 routeip routenet-tools 未保守 → iproute2 netstatssnet-tools 未保守 → iproute2 deprecation は環境で異なるので、 断定前に現行 doc で確認する(出典: Red Hat "Deprecated Linux command replacements")。 なお
nslookupは obsolete ではない: 現行 man page (bind9-dnsutils) に非推奨記述はなく、 dig と同一 package。 script では出力が扱いやすい dig が好まれるが、 これは好みで deprecation ではない。