シェルスクリプトを書いていると、次のようなエラーに遭遇することがあります。
./script.sh: line 10: ${VAR//foo/bar}: bad substitution
一見すると単純な文法ミスのように見えますが、これは シェルの種類や互換性の問題 が原因であることが多いです。
本記事では、このエラーの原因、再現例、Bash と sh/zsh との違い、対処法、実運用での注意点を詳しく解説します。
1. エラーの背景と原因
「bad substitution」は、シェルが変数展開構文を理解できないときに発生します。特に ${VAR//pattern/replacement}
という「全置換」構文は POSIX 標準ではなく、Bash 独自機能 です。そのため、シェバンが /bin/sh
だったり、zsh で互換モードが有効だったりするとエラーになります。
また、CI/CD 環境や Docker コンテナでは /bin/sh
が dash であることが多く、ローカルでは動いていたスクリプトが本番で突然壊れるといったケースもあります。
2. 再現例と実際のエラーメッセージ
再現は簡単で、/bin/sh
で実行すると次のようになります。
#!/bin/sh
VAR="foo-bar-foo"
echo ${VAR//foo/baz}
実行結果:
./sample.sh: 3: Bad substitution
一方で /bin/bash
で実行すると正常に動作します。
#!/bin/bash
VAR="foo-bar-foo"
echo ${VAR//foo/baz}
# => baz-bar-baz
つまり、どのシェルでスクリプトを実行しているかが重要 です。
3. 対処法1: シェバンをBashに変更
最もシンプルな解決策は、スクリプトの先頭行を Bash に変更することです。
#!/usr/bin/env bash
この方法なら、POSIX sh ではサポートされない拡張構文も問題なく動作します。ただし、システムに Bash がインストールされていない環境(軽量 Alpine Linux など)では別途インストールが必要です。
4. 対処法2: POSIX 互換構文に書き換える
Bash を使えない場合、sed
や parameter expansion
の他の形を使って置換を実現します。
#!/bin/sh
VAR="foo-bar-foo"
VAR=$(echo "$VAR" | sed 's/foo/baz/g')
echo "$VAR"
この方法は POSIX 互換のため、sh でも zsh でも動作します。パフォーマンスは若干落ちますが、ポータビリティを優先したいスクリプトではこちらが安全です。
5. 実運用での注意点
チーム開発や CI/CD パイプラインでは、シェルが統一されていないと再現困難なバグが発生します。次の点を意識するとトラブルを防げます。
- すべてのスクリプトで
#!/usr/bin/env bash
か#!/bin/sh
を明示 - CI 環境のシェルをドキュメント化
- Dockerfile 内で
RUN apk add bash
などを行い Bash を確実に利用可能にする shellcheck
を利用して静的解析し、非互換構文を事前に検知
6. まとめ
「bad substitution」エラーは単なるスペルミスではなく、実行シェルが構文を理解できないことが原因 です。Bash 特有の構文を使っている場合は、シェバンを Bash にするか POSIX 互換構文に書き換えることで解決できます。実運用ではシェルの種類を統一し、CI/CD 環境とローカルで挙動が揃うようにすることが重要です。
参考
- Bash Parameter Expansion [https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html]
- ShellCheck – Shell script analysis tool [https://www.shellcheck.net/]
- POSIX Shell Command Language [https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html]