RHEL では問題なく動いていたのに、Ubuntu に持っていったとたん構文エラーが出る。この現象で困ったことはないでしょうか。
原因のほとんどは、シェバン行(shebang)の意味を正確に理解していないことにあります。
#!/bin/sh はすべての環境で bash を呼び出すわけではありません。Ubuntu では /bin/sh が dash(軽量POSIXシェル)に向いているため、bash 固有の構文(bashism)を含むスクリプトが即座に壊れます。この記事では、
#!/bin/sh と #!/bin/bash の本質的な違いから、Ubuntu の dash 環境で壊れる典型パターン、POSIX準拠の書き方、そして checkbashisms コマンドによる自動検出まで、sh bash 違い posix の観点から移植性の高いスクリプト設計を体系的に解説します。動作確認環境: Ubuntu 24.04 LTS(dash 0.5.12)/ RHEL 9.4(bash 5.1.8)
この記事のポイント
・#!/bin/sh は OS 標準シェルを使う指定。Ubuntu では dash、RHEL では bash が実行される
・bash 固有構文(bashism)を使うと Ubuntu の dash で構文エラーになる
・POSIX準拠で書けば sh/bash どちらの環境でもそのまま動く移植性になる
・checkbashisms コマンドで bashism を事前に自動検出できる
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
#!/bin/sh と #!/bin/bash の本質的な違い
シェルスクリプトの1行目に書く #!(シャープ+エクスクラメーション)をシェバン(shebang)と呼びます。OS はこの行を読んで、スクリプトをどのプログラムで実行するかを決めます。
#!/bin/bash であれば「必ず /bin/bash で実行する」と明示されます。一方、#!/bin/sh は「システム標準シェル(/bin/sh)で実行する」という指定です。ここが問題の出発点です。
1. /bin/sh の実体は OS によって異なる
/bin/sh の実体がどのシェルに向いているかは、OS ごとに異なります。
# Ubuntu 24.04 LTS で確認 $ ls -la /bin/sh lrwxrwxrwx 1 root root 4 Jan 24 2024 /bin/sh -> dash # RHEL 9.4 / Rocky Linux 9 で確認 $ ls -la /bin/sh lrwxrwxrwx 1 root root 4 May 11 2022 /bin/sh -> bash
Ubuntu では /bin/sh が dash(Debian Almquist Shell)に向いています。dash は POSIX 準拠を重視した軽量シェルで、bash の拡張構文の多くをサポートしていません。RHEL 系では /bin/sh が bash そのものなので、bash 固有の構文を使っても動いてしまいます。
2. dash が /bin/sh に採用されている理由
Ubuntu が dash を /bin/sh に採用しているのは、起動速度と POSIX 準拠の徹底を重視しているためです。システム起動時の init スクリプト群を dash で動かすことで、ブートシーケンスを高速化しています。bash は多機能な反面、インタプリタの起動オーバーヘッドが dash より大きくなります。
つまり、#!/bin/sh と書いたスクリプトが「RHEL では動くが Ubuntu では動かない」という現象は、シェルの差ではなく sh bash 違い posix の設計上の問題として理解する必要があります。
シェルスクリプト講座を見る >>
Ubuntu(dash)で壊れる典型パターン
bash 固有の構文を「bashism(バッシズム)」と呼びます。以下は現場でよく見かける bashism のパターンです。#!/bin/sh を指定したスクリプトにこれらを使うと、Ubuntu の dash では構文エラーになります。
1. 二重ブラケット [[ ]] を使っている
[[ ]](二重ブラケット)は bash および ksh の拡張構文で、POSIX には含まれていません。dash は解釈できずにエラーを出します。
# NG: bashism(dash では動かない) if [[ "$str" == "hello" ]]; then echo "matched" fi # OK: POSIX準拠(= は1つ、[ ] は1つ) if [ "$str" = "hello" ]; then echo "matched" fi
また、文字列比較の演算子も POSIX は =(シングルイコール)です。[ "$var" == "value" ] の == は bash が受け付けますが、POSIX 規格では未定義です。
2. source コマンドを使っている
source は bash の組み込みコマンドで、POSIX には含まれていません。POSIX での等価なコマンドはドット(.)です。
# NG: bashism source /etc/profile.d/myapp.sh # OK: POSIX準拠(ドットコマンド) . /etc/profile.d/myapp.sh
3. echo -e でエスケープシーケンスを使っている
echo -e の動作は POSIX で規定されていません。実装によって動作が異なるため、エスケープシーケンスを使いたい場合は printf を使うのが正解です。
# NG: 動作が環境依存 echo -e "1行目\n2行目" # OK: POSIX準拠、動作が確実 printf "1行目\n2行目\n"
4. 配列(Array)を使っている
bash の配列構文はPOSIX未定義です。dash では arr=() の形式を解釈できません。
# NG: bashism(配列はPOSIX外) arr=(alpha beta gamma) echo "${arr[0]}" # OK: POSIX準拠(位置パラメータを使う) set -- alpha beta gamma echo "$1" # 複数の値をスペース区切り文字列で管理する方法も有効 ITEMS="alpha beta gamma" for item in $ITEMS; do echo "$item" done
5. 算術展開で (( )) を使っている
(( ))(二重丸括弧)による算術評価も bashism です。POSIX では $(( )) を使います。
# NG: bashism (( count++ )) if (( count > 10 )); then echo "limit"; fi # OK: POSIX準拠 count=$(( count + 1 )) if [ "$count" -gt 10 ]; then echo "limit"; fi
POSIX準拠で書くための実践ルール
POSIX準拠のスクリプトを書く際の判断基準をまとめます。「移植性が必要かどうか」で #!/bin/sh と #!/bin/bash を使い分けることが設計の第一歩です。
1. シェバン行の使い分け基準
以下を判断基準にしてください。
・#!/bin/bash を使う場面:そのスクリプトが bash 環境専用であることが確定している場合。配列・[[ ]]・プロセス置換など bash の便利機能を積極的に使いたい場合。・#!/bin/sh を使う場面:複数の Linux ディストリビューションや BSD 系 OS で動かす可能性がある場合。OS のシステムスクリプト(
/etc/init.d/ 配下など)として配置する場合。インフラの自動化スクリプトで環境差異を吸収したい場合。2. POSIX 準拠の代替構文早見表
| やりたいこと | bashism(NG) | POSIX準拠(OK) |
|---|---|---|
| 条件分岐(拡張) | [[ "$x" == "y" ]] |
[ "$x" = "y" ] |
| ファイル読み込み | source file.sh |
. file.sh |
| エスケープ出力 | echo -e "foo\nbar" |
printf "foo\nbar\n" |
| 配列 | arr=(a b c) |
位置パラメータ or 文字列 |
| 算術評価(条件) | (( count > 10 )) |
[ "$count" -gt 10 ] |
| 算術演算 | (( count++ )) |
count=$(( count + 1 )) |
| 正規表現マッチ | [[ "$x" =~ ^[0-9]+$ ]] |
printf "%s" "$x" | grep -qE '^[0-9]+$' |
| 文字列長 | ${#str} |
${#str}(POSIX対応) |
3. 関数内のローカル変数
local キーワードは POSIX 2018 から正式に追加されました。現在の主要ディストリビューション(Ubuntu 24.04 / RHEL 9)では dash でも local が使えます。ただし、宣言と代入を同時に行うパターンには注意が必要です。
# 宣言と代入を分ける(より安全) my_func() { local result result=$(some_command) echo "$result" } # コマンド置換と同時に local 宣言すると終了コードが失われる落とし穴 # (bash も dash も同様の注意が必要) my_func_bad() { local result=$(some_command) # some_command が失敗しても終了コードは 0 echo "$result" }
checkbashisms コマンドで bashism を自動検出する
checkbashisms は Debian 系のツールで、シェルスクリプト内の bashism を自動的に検出します。CI/CD パイプラインに組み込むことで、意図しない bashism の混入を事前に防げます。
1. インストール方法
# Ubuntu / Debian sudo apt install devscripts # RHEL 9 / Rocky Linux 9(EPELリポジトリが必要) sudo dnf install epel-release sudo dnf install devscripts
2. 基本的な使い方
# スクリプトをチェックする checkbashisms script.sh # 警告も含めて詳細に表示する checkbashisms -f script.sh # 複数ファイルを一括チェック checkbashisms scripts/*.sh
3. 実際の検出例
以下のような bashism が含まれるスクリプトを作成して確認します。
#!/bin/sh # test_bashism.sh arr=(alpha beta gamma) echo "${arr[0]}" if [[ "$1" == "debug" ]]; then echo "debug mode" fi source /etc/os-release
checkbashisms test_bashism.sh を実行した実際の出力です。
possible bashism in test_bashism.sh line 4 (bash array): arr=(alpha beta gamma) possible bashism in test_bashism.sh line 5 (bash array element): echo "${arr[0]}" possible bashism in test_bashism.sh line 7 (double bracket): if [[ "$1" == "debug" ]]; then possible bashism in test_bashism.sh line 11 (should be '.', not 'source'): source /etc/os-release
このように、使用箇所と行番号を特定して報告してくれます。実務では CI の lint フェーズに checkbashisms を組み込み、コードレビュー前に機械的に検出する運用を推奨します。
4. checkbashisms が検出しない落とし穴
checkbashisms は静的解析ツールのため、実行時の動的な挙動は検出できません。また、echo の -e オプションについては環境依存で動作するため、「bashism とは断定できない」として警告が出ないケースもあります。ツールを通過しても、最終的には dash 実環境での動作確認を行う習慣をつけてください。
# dash を使って直接テスト実行する(Ubuntu 以外の環境でも確認可能) dash script.sh # または dash を明示してデバッグ出力も有効にする dash -x script.sh
移植性を維持したスクリプト設計のまとめ
sh bash 違い posix の観点から、移植性の高いスクリプト設計の要点をまとめます。
| 判断ポイント | 推奨するアプローチ |
|---|---|
| シェバン行の選択 | 移植性が必要なら #!/bin/sh、bash 専用なら #!/bin/bash |
| 条件分岐 | [ ] を使い、文字列比較は =(シングル)を使う |
| ファイル読み込み | source ではなく . filename を使う |
| 出力 | エスケープが必要なときは printf を使う |
| 配列 | POSIX sh では使えない。位置パラメータや改行区切り文字列で代用 |
| 算術演算 | $(( )) を使う((( )) は避ける) |
| 検証 | checkbashisms で静的解析 + dash script.sh で実動確認 |
現場でよく見るパターンとして、RHEL 系だけで開発・テストしているチームが Ubuntu ベースの本番環境に初めてデプロイしたときに、ここで解説した bashism 起因のエラーが一気に噴出するケースがあります。スクリプト管理リポジトリに checkbashisms の CI チェックを追加するだけで、この種の問題の大半は事前に防げます。
シェルスクリプトの移植性設計をさらに深めたい方は、シェルスクリプト実践講座(Linux Master Pro)もご覧ください。現場で使える POSIX 設計パターンから CI 組み込みまで体系的に学べます。
シェルスクリプト講座を見る >>
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
登録10秒/合わなければ解除3秒 / 詳細はこちら
- 次のページへ:シェルスクリプトのセキュリティ設計|クォート徹底・コマンドインジェクション対策・evalを避ける書き方
- 前のページへ:readとselectで対話式シェルスクリプトを作る方法|確認プロンプト・メニュー選択・入力検証の設計
- この記事の属するカテゴリ:シェルスクリプトへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は1分、解除もいつでも可。
登録無料・いつでも解除できます