カーネル実行と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 チェックに落とし込めば、現場のトラブルは大幅に減らせます。