カーネル実行とbash実行の違い
要点:
./script.shは「カーネルがファイルを実行」し、bash script.shは「bash がファイルを読み取って解釈する」。前者は execve と x ビット、後者は open/read と r ビットが主役。
はじめに(前提と結論)
OS の挙動を正確に掴むには、「誰が exec するのか」「どの権限ビットを見るのか」「shebang(#!)は誰がいつ解釈するのか」を分けて理解するのが近道です。
-
./script.sh:親シェルがexecve("./script.sh")をカーネルに依頼し、カーネルが実行可否を判定します。ファイルに 実行権限(x) が必要。先頭が#!なら カーネルが指定インタプリタ(例:/usr/bin/env bash)を起動して引数にスクリプトを渡します。 -
bash script.sh:親シェルはexecve("/bin/bash", ["bash", "script.sh"])を呼び、bash プロセスが自分でファイルを開いて読み取るだけ。必要なのは 読み取り権限(r)。shebang は コメント扱いで基本的に無視され、bash の言語仕様で解釈されます。
結論として、OS カーネルの実行パスに乗せるか(execve)/ユーザ空間のインタプリタに読み込ませるかが本質です。この違いは、権限、マウントオプション、エラーパターン、セキュリティ制御、ポータビリティに直結します。
技術的な流れの違い(システムコール観点)
1) ./script.sh(直接実行)
- 親シェルが
fork→ 子プロセスがexecve("./script.sh", argv, envp)をカーネルに依頼。 - カーネルは当該ファイルの x ビット、実効 UID/GID、ACL、マウントフラグ(
noexecなど)を検査。要件を満たさない場合は EACCES。 - ファイル先頭が
#!の場合、カーネルが shebang を解釈し、指定インタプリタ(+shebang 行に書かれた追加引数)を 新たに execve。argv[0]はインタプリタ、argv[1]以降にスクリプトパス等が渡されます。 -
#!なしで ELF/COFF など実行形式でもテキストでもない場合は ENOEXEC。呼び出し元のシェル実装によっては/bin/sh script.shなどへフォールバックすることがあります(ポータビリティは要注意)。 -
noexecマウント(例:/tmpにnoexec)ではここでブロック。たとえxが立っていても 「実行」自体を禁止できるのがカーネル経路の強みです。
2) bash script.sh(インタプリタ明示)
- 親シェルが
fork→ 子がexecve("/bin/bash", ["bash","script.sh"], envp)を発行。 - カーネルは **bash バイナリ(ELF)**の実行を許可すれば OK(当然
x必須)。 - 起動した bash 自身がユーザ空間で
open("script.sh")→readし、内容を bash 文法として解釈・実行。 - スクリプトファイルに必要なのは r ビットのみ。shebang は コメント扱い。
noexecマウントでも読めれば実行可能(実行しているのは bash バイナリであって、スクリプトは「読み物」だから)。
直接実行 vs インタプリタ実行:比較表(同階層の差分を一望)
基本特性
| 観点 |
./script.sh(カーネル実行) |
bash script.sh(bash 実行) |
|---|---|---|
| 要求権限 | スクリプトに x(実行) 必須(多くは r も必要) | スクリプトは **r(読み取り)**のみで可 |
| shebang | カーネルが解釈し、指定インタプリタを exec | 無視(コメント)。bash 文法で解析 |
| noexec マウント | ブロックされる(実行不可) | 回避可能(bash が読むだけ) |
| エントリポイント | スクリプト自身(shebang 経由でインタプリタへ) | 常に /bin/bash(または指定の bash) |
| PATH/ポータビリティ |
#!/usr/bin/env bash で PATH 解決可 |
呼び出した bash に依存(shebang無視) |
セキュリティ/制御
| 観点 | ./script.sh |
bash script.sh |
|---|---|---|
| SELinux/AppArmor | exec トランジションが発生しラベル遷移が効く | ファイルは 単なる読み物で遷移対象外になりがち |
| setuid/capabilities | スクリプトの setuid は 無効化が一般的 | 同左(bash 経由でも昇格しない) |
| 監査(auditd) | exec の被疑要因が明確に残りやすい | bash 内部実行で粒度が粗くなることあり |
| マウント制御 |
noexec で一括抑止が容易 |
読み取り可能なら実行され得る |
運用/デバッグ
| 観点 | ./script.sh |
bash script.sh |
|---|---|---|
| 代表エラー |
Permission denied(x 無/ noexec)、bad interpreter(shebang/改行) |
Permission denied(r 無)、言語不一致の構文エラー
|
| 改行(CRLF)罠 |
bad interpreter: No such file or directory になりやすい |
shebang無視のため 表面上動く→潜在バグ温存 |
| shebang オプション |
#!/usr/bin/env bash -e 等が 正しく効く
|
効かない(set -e 等はスクリプト側で記述) |
| 再現性 | shebang で実行系固定 → 高めやすい | 呼び出し側 bash に依存 → 環境差異を拾いやすい |
パフォーマンス/プロセス
| 観点 | ./script.sh |
bash script.sh |
|---|---|---|
| 起動経路 |
execve(script)→(shebang)→execve(interpreter)
|
execve(bash)→open/read(script)
|
| プロセスツリー | interpreter が 子として明確 | すべて bash の内部に吸収 |
| オーバーヘッド | 追加の exec が入る可能性 | I/O 読み取り分のオーバーヘッド |
実務で効く差分(意思決定のコア)
-
配布用スクリプトは「shebang + x ビット + 直接実行」を標準化
- shebang は可搬性重視なら
#!/usr/bin/env bash、再現性重視なら/bin/bash固定。 - CI で以下をチェック:shebang あり、改行 LF、権限
0755、shellcheck合格、依存コマンドの存在。 - こうすることで shebang オプション(
-euo pipefail等)が確実に効く。
- shebang は可搬性重視なら
-
noexec な領域・権限事情がある現場では一時的に
bash script.sh- 読み取りさえできれば動く。だが shebang に書いたオプションや別言語指定は効かない。
- 運用規程として「緊急対応でのみ可・恒常化しない」を明文化すると事故を減らせる。
-
言語ミスマッチの防止
- Python/Node/Perl などは 必ず shebang を正しく書き、x を付与。
bash script.pyの誤実行を撲滅。 -
.sh拡張子の有無は OS 的には無関係だが、チーム規約で可読性を担保。
- Python/Node/Perl などは 必ず shebang を正しく書き、x を付与。
-
エラー駆動のトラブルシュート
-
Permission denied→ x 無 ornoexecの疑い。 -
bad interpreter→ shebang のパス/改行(CRLF)/FS 破損を疑う。 -
bash script.shでだけ成功 →noexec回避 or shebang/改行の潜在不良。
-
-
セキュリティ境界の設計
- 実行抑止は
noexecと x ビットでまず担保。bash script.sh経路を想定し、ACL/SELinux で読み取り自体を制御する設計も検討。 - 監査要件が強い場合は 直接実行を標準にし、exec の証跡を取りやすくする。
- 実行抑止は
具体例で腹落ち(最小実験)
# x なし:直接は失敗、bash 経由は成功
printf '#!/usr/bin/env bash\necho OK\n' > /tmp/x.sh
chmod 0644 /tmp/x.sh
/tmp/x.sh # → Permission denied
bash /tmp/x.sh # → OK
# CRLF 罠:直接は bad interpreter、bash 経由は通ってしまう
printf '#!/usr/bin/env bash\r\necho CRLF\r\n' > /tmp/crlf.sh
chmod +x /tmp/crlf.sh
/tmp/crlf.sh # → bad interpreter: No such file or directory
bash /tmp/crlf.sh# → CRLF(動いてしまう)
# 言語不一致:Python を bash で実行すると壊れる
printf '#!/usr/bin/env python3\nprint("py")\n' > /tmp/a.py
chmod +x /tmp/a.py
/tmp/a.py # → py
bash /tmp/a.py # → bash: syntax error ...
深掘りポイント(中級者が押さえるべき“地雷”)
-
shebang の引数:
#!/usr/bin/env -S bash -E -u -o pipefailのように-Sで分割引数を許す実装もあります(環境差要確認)。直接実行でのみ効き、bash script.shでは効きません。 -
argv の違い:直接実行では
argv[0]がインタプリタに置き換わり、argv[1]にスクリプトパスが入る(shebang の追加引数はさらに前に入る)。$0の値が運用に影響するツールでは違いが顕在化します。 -
リーダブルな権限制御:
noexecは強力だが、**読み取りを許せばバイパス(bash 経由)**されます。読み取り禁止(r を落とす)、ディレクトリに 実行権(検索権)x が無いとファイルを開けない点なども組み合わせて制御する。 -
環境依存の /bin/bash 問題:コンテナ/軽量ディストロでは
/bin/bashが無いことも。/usr/bin/env bashを使うか、ランタイムを同梱して再現性を上げる。 - 監査・可観測性:直接実行は「どのインタプリタで exec されたか」がログで追いやすい。bash 実行は「bash が何を読んだか」を別途監視しないと霧散しやすい。
使い分けの実務指針(ミニフロー)
- 配布 or 自動化ジョブ:shebang + x で 直接実行を標準。
- 一時ディレクトリ/制約環境:読み取りのみ確保できるなら bash 実行で暫定対応。ただし恒久化しない。
-
異言語スクリプト:shebang で実行系を固定、
bash <file>は禁止を規約化。 -
セキュリティ要件強:
noexec+権限・MAC で直接実行を設計に組み込み、読み取り権限の最小化で bash 経由も抑制。
まとめ(実用スナップショット)
- 軸は execve か open/read か:前者は x ビットと shebang をカーネルが解釈、後者は r ビットで bash が解釈。
-
マウント/権限/監査の効き方が根本的に違う:
noexecは直接実行を止めるが bash 読み込みは止めない。 -
配布は「shebang + x + 直接実行」を標準:再現性・監査・事故防止で有利。
bash script.shは 例外運用に留める。 -
デバッグはメッセージで即切り分け:
Permission denied(x/noexec)、bad interpreter(shebang/改行)、言語不一致(bash 経由)。
この枠組みをチームのコーディング規約と CI チェックに落とし込めば、現場のトラブルは大幅に減らせます。