#!/bin/shと#!/bin/bashの違いとPOSIX互換スクリプトの書き方|dash環境で壊れないための移植性設計

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)シェルスクリプト > #!/bin/shと#!/bin/bashの違いとPOSIX互換スクリプトの書き方|dash環境で壊れないための移植性設計
「#!/bin/sh で書いたシェルスクリプトが、Ubuntu でだけ動かない。」
RHEL では問題なく動いていたのに、Ubuntu に持っていったとたん構文エラーが出る。この現象で困ったことはないでしょうか。

原因のほとんどは、シェバン行(shebang)の意味を正確に理解していないことにあります。#!/bin/sh はすべての環境で bash を呼び出すわけではありません。Ubuntu では /bin/shdash(軽量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 を事前に自動検出できる


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
図解60P/登録10秒/解除も3秒 / 詳細はこちら

#!/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/shdash(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 の設計上の問題として理解する必要があります。

現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、20年以上の運用経験を持つ現役エンジニアが基礎から教えます。
シェルスクリプト講座を見る >>

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 組み込みまで体系的に学べます。

現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、20年以上の運用経験を持つ現役エンジニアが基礎から教えます。
シェルスクリプト講座を見る >>

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は1分、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

登録10秒/合わなければ解除3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。