シェルスクリプトの実行方法は「./script.sh」「bash script.sh」「sh script.sh」「source script.sh(. script.sh)」の4通りで、違いは「実行権限が要るか」「1行目のシェバンが使われるか」「別プロセスで動くか」の3点です。 シェバン(shebang)とは、1行目に書く #!/bin/bash のことで、「このファイルをどのプログラムに読ませるか」をOSに伝える行です。

この記事では、4通りの実行方法を同じスクリプトで動かし、変数やcdが残る/残らないを実際の出力で比べます。あわせて #!/bin/bash・#!/usr/bin/env bash・#!/bin/sh の違い、Permission denied(終了コード126)や command not found(127)などのエラー文から対処を引ける表も用意しました。出力はすべて Ubuntu 24.04.4 LTS、GNU bash 5.2.21、dash(/bin/sh)で実行したものです。ユーザー名とパスの表示は環境に合わせて置き換えています(taro、/home/taro/work)。

シェルスクリプトの実行方法の早見表

実行方法 実行権限(x) シェバン 動くプロセス 変数・cd・exportが呼び出し元に残るか 向いている用途
./script.sh 必要 使われる 別プロセス(子) 残らない 完成したスクリプトをコマンドとして使う
bash script.sh 不要(読み取り権限のみ) 使われない(bashが読む) 別プロセス(子) 残らない 権限を付ける前の試し実行、bash -x でのデバッグ
sh script.sh 不要(読み取り権限のみ) 使われない(shが読む) 別プロセス(子) 残らない POSIX sh向けに書いたスクリプト
source script.sh / . script.sh 不要(読み取り権限のみ) 使われない(今のシェルが読む) 今のシェル自身 残る 環境変数の読み込み、関数定義の取り込み

シェバンの書き方は次の3つを押さえれば足ります。

シェバン 意味 選ぶ場面
#!/bin/bash /bin/bash を直接指定 Linuxサーバー向け。配列や [[ ]] などbashの機能を使う
#!/usr/bin/env bash PATH から bash を探して使う macOS・BSD・Nixなど bash の場所が異なる環境でも動かしたい
#!/bin/sh システムの sh を使う(Ubuntuでは dash) POSIX sh の範囲だけで書いた軽いスクリプト

エラー文から探す場合はこちらです(詳しくは後半の対処表へ)。

エラー文(bash 5.2) 終了コード まず確認すること
Permission denied 126 ls -l で x が付いているか → chmod +x
command not found 127 ./ を付けたか、PATH にあるか
No such file or directory 127 ファイル名・今いるディレクトリ
cannot execute: required file not found(以前の版では bad interpreter) 127 シェバンのパス、改行コードがCRLFになっていないか
x が付いているのに Permission denied 126 findmnt で noexec マウントか確認

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「シェルスクリプトの実行方法4通りの違いとシェバンの意味 エラー別対処つき」

シェバンの意味と仕組み

1行目の「#!」をOSが読んでインタープリタを決める

シェバンは、ファイルの先頭2バイトが #! のときに働きます。./script.sh のようにファイルを直接実行すると、OS(カーネル)が1行目を読み、そこに書かれたプログラムを起動して、スクリプトのパスを引数として渡します。

flowchart TD
    A["./hello.sh を実行"] --> B{"実行権限 x がある?"}
    B -- ない --> E1["Permission denied (126)"]
    B -- ある --> C{"先頭2バイトが #! か"}
    C -- はい --> D{"書かれたパスのプログラムが存在するか"}
    D -- ある --> F["/bin/bash ./hello.sh として起動"]
    D -- ない --> E2["cannot execute: required file not found (127)"]
    C -- いいえ --> G["呼び出し元のシェルが自分で読んで実行"]

つまり #!/bin/bash と書いたファイルを ./hello.sh で動かすと、内部では /bin/bash ./hello.sh と同じことが起きています。シェルから見ると # で始まる行はコメントなので、bash がこの行を読んでも何も起きません。シェバンは「OS向けの指示」であり「シェル向けにはコメント」という二重の役割を持っています。

シェバンはシェル以外にも使える

シェバンに書くのはシェルに限りません。Python を指定すれば、拡張子が .sh でも Python として動きます。

#!/usr/bin/python3
print("py")
$ ./p.sh
py
$ bash p.sh
p.sh: line 2: syntax error near unexpected token `"py"'
p.sh: line 2: `print("py")'

./p.sh はシェバンに従って Python が読み、bash p.sh はシェバンを使わずに bash が読むので構文エラーになります。拡張子ではなく、シェバンと実行方法が「誰が読むか」を決めます。

シェバンがないファイルを ./ で実行するとどうなるか

シェバンのないファイルに実行権限を付けて動かすと、呼び出したシェルが代わりに読みます。次のスクリプトで確かめました。

echo "run by: $(ps -p $$ -o comm=)"
$ bash -c './noshebang.sh'
run by: bash
$ sh -c './noshebang.sh'
run by: sh

呼び出し元が bash なら bash、sh なら sh と、読み手が実行した人の環境で変わります。cron やほかのプログラムから呼ばれたときに挙動が変わる原因になるので、1行目には必ずシェバンを書いておくと安心です。

4通りの実行方法を実行結果で比べる

確認用のスクリプト

次の hello.sh を使います。変数 MSG を設定し、/tmp に移動して、自分のプロセスID($$)と bash のバージョン、現在のディレクトリを表示します。

#!/bin/bash
MSG="hello from script"
cd /tmp
echo "pid=$$ shell=$BASH_VERSION pwd=$(pwd)"

これを4通りの方法で順に動かし、そのたびに呼び出し元で MSG と現在のディレクトリを表示する driver.sh を用意しました。

cd "$1"
echo "parent pid=$$"
echo "--- ./hello.sh";      ./hello.sh;      echo "MSG=[${MSG}] pwd=$(pwd)"
echo "--- bash hello.sh";   bash hello.sh;   echo "MSG=[${MSG}] pwd=$(pwd)"
echo "--- sh hello.sh";     sh hello.sh;     echo "MSG=[${MSG}] pwd=$(pwd)"
echo "--- source hello.sh"; source hello.sh; echo "MSG=[${MSG}] pwd=$(pwd)"
$ chmod +x hello.sh
$ bash driver.sh /home/taro/work
parent pid=663
--- ./hello.sh
pid=664 shell=5.2.21(1)-release pwd=/tmp
MSG=[] pwd=/home/taro/work
--- bash hello.sh
pid=667 shell=5.2.21(1)-release pwd=/tmp
MSG=[] pwd=/home/taro/work
--- sh hello.sh
pid=670 shell= pwd=/tmp
MSG=[] pwd=/home/taro/work
--- source hello.sh
pid=663 shell=5.2.21(1)-release pwd=/tmp
MSG=[hello from script] pwd=/tmp

この出力から読み取れることを整理します。

実行方法 スクリプト内の pid 親(663)と同じか BASH_VERSION 実行後の MSG 実行後の pwd
./hello.sh 664 別 5.2.21 空 元のまま
bash hello.sh 667 別 5.2.21 空 元のまま
sh hello.sh 670 別 空(dashが実行) 空 元のまま
source hello.sh 663 同じ 5.2.21 残る /tmp に移動したまま
  • ./・bash・sh は子プロセスで動くため、スクリプト内の変数代入や cd は呼び出し元に影響しません。
  • source は今のシェル自身が1行ずつ読むため、pid が親と同じで、変数も cd もそのまま残ります。
  • sh hello.sh では BASH_VERSION が空でした。シェバンに #!/bin/bash と書いてあっても、sh を前に付けるとシェバンは使われず dash が読みます。

./script.sh:シェバンに従う標準の実行方法

./script.sh は、実行権限とシェバンの両方を使う実行方法です。権限がないと次のようになります。

$ ./hello.sh
bash: line 1: ./hello.sh: Permission denied
$ echo $?
126
$ chmod +x hello.sh
$ ls -l hello.sh
-rwxr-xr-x 1 taro taro 89 Oct  3 13:30 hello.sh
$ ./hello.sh
pid=664 shell=5.2.21(1)-release pwd=/tmp

先頭の ./ は「今いるディレクトリの」という意味です。./ を付けずに hello.sh とだけ打つと、シェルは PATH に登録されたディレクトリだけを探すため、目の前にファイルがあっても見つかりません。

$ hello.sh
bash: line 1: hello.sh: command not found
$ echo $?
127

PATH の通ったディレクトリに置けば、./ も拡張子もなしでコマンドのように呼べます。

$ mkdir -p bin && cp hello.sh bin/hello
$ PATH="$PWD/bin:$PATH"; hello
pid=982 shell=5.2.21(1)-release pwd=/tmp

bash script.sh:実行権限なしでも動かせる

bash script.sh は bash を起動し、スクリプトを「読み込むファイル」として渡します。ファイルは読めれば十分なので、x の権限は不要です。シェバンはコメントとして読み飛ばされます。

次のような場面で便利です。

場面 コマンド 補足
権限を付ける前に試す bash script.sh 配布物をまず確かめたいとき
1行ずつ実行内容を表示する bash -x script.sh 展開後のコマンドが + 付きで出る
構文だけ確認する bash -n script.sh 実行せずに構文チェック(問題なければ何も出ず終了コード0)
noexec の場所にあるスクリプトを動かす bash /path/script.sh 後述の noexec の節を参照

bash -x の出力例です。

$ bash -x hello.sh
+ MSG='hello from script'
+ cd /tmp
++ pwd
+ echo 'pid=997 shell=5.2.21(1)-release pwd=/tmp'
pid=997 shell=5.2.21(1)-release pwd=/tmp

sh script.sh:Ubuntu では dash が読むので bash の文法は通らない

Ubuntu や Debian では /bin/sh は dash へのシンボリックリンクです。

$ ls -l /bin/sh
lrwxrwxrwx 1 root root 4 Mar 31  2024 /bin/sh -> dash

dash は POSIX sh の範囲に絞った軽量なシェルで、配列や [[ ]] は持っていません。次の arr.sh で比べます。

#!/bin/bash
fruits=(apple banana)
echo "count=${#fruits[@]}"
if [[ "abc" == a* ]]; then echo "match"; fi
$ ./arr.sh
count=2
match
$ bash arr.sh
count=2
match
$ sh arr.sh
arr.sh: 2: Syntax error: "(" unexpected
$ echo $?
2

「./ では動くのに sh を付けると Syntax error になる」ときは、シェバンが bash なのに dash で読ませていることが原因です。対処は次のどちらかです。

方針 やること
bash の機能を使い続ける ./script.sh か bash script.sh で実行する。手順書や cron の記述も sh から bash に直す
sh でも動くようにする 配列・[[ ]]・source・function キーワードなどを POSIX の書き方に置き換え、シェバンを #!/bin/sh にする

なお、/bin/sh の実体はディストリビューションで異なります。RHEL系や macOS では別のシェルが sh として動くとされていますが、この記事では未実行です(Ubuntu 24.04 の dash のみ確認)。

source script.sh(. script.sh):今のシェルに読み込む

source は bash の組み込みコマンドで、ファイルの中身を今のシェルにそのまま流し込みます。.(ドット1文字)は同じ働きをする POSIX の書き方です。

$ type source
source is a shell builtin
$ type .
. is a shell builtin

環境変数を設定するファイルで違いがはっきり出ます。env.sh の中身は export APP_ENV=dev の1行です。

$ bash env.sh; echo "[$APP_ENV]"
[]
$ . ./env.sh; echo "[$APP_ENV]"
[dev]

bash env.sh は子プロセスの中で export して終わるので、呼び出し元には何も残りません。環境変数を今のシェルに設定したいときは source(.)を使います。 .bashrc の変更を反映する source ~/.bashrc や、Python 仮想環境の source .venv/bin/activate が source で実行するのはこのためです。

source を使うときに知っておきたい点を、実行結果つきで3つ挙げます。

1. exit を書くと今のシェルごと終了する

$ cat ex.sh
echo in-script
exit 3
$ bash -c 'bash ex.sh; echo "after bash: $?"; source ex.sh; echo "never"'
in-script
after bash: 3
in-script

bash ex.sh は子プロセスが終了コード3で終わるだけですが、source ex.sh では exit が呼び出し元のシェルに効き、次の echo "never" は実行されませんでした。ターミナルで試すとウィンドウが閉じることがあります。source 用のファイルでは return を使うとその場で読み込みだけを終えられます。どちらの呼び方でも動かしたいときは次の書き方が使えます。

echo before
return 0 2>/dev/null || exit 0
echo after
$ bash -c 'source ret.sh; echo "still alive"'
before
still alive

2. $0 が変わる

#!/bin/bash
echo "0=$0 src=${BASH_SOURCE[0]}"
$ ./who.sh
0=./who.sh src=./who.sh
$ bash who.sh
0=who.sh src=who.sh
$ source who.sh
0=bash src=who.sh

source では $0 が呼び出し元のシェル名(bash)になります。スクリプト自身の場所を知りたいときは ${BASH_SOURCE[0]} を使うと、どの方法でも同じように取れます。

3. sh には source がなく、. はパスの書き方に注意する

$ sh -c 'source env.sh; echo "[$APP_ENV]"'
sh: 1: source: not found
[]
$ sh -c '. env.sh'
sh: 1: .: env.sh: not found
$ sh -c '. ./env.sh; echo "[$APP_ENV]"'
[dev]

dash には source がなく、. を使います。さらに dash の . はスラッシュを含まない名前を PATH から探すため、. ./env.sh のように ./ を付けるのが確実です。

引数の渡し方はどの方法でも同じです。source でも引数を付ければ $1、$2 として受け取れます。

$ ./args.sh a b
args: a b / count=2
$ source args.sh x y
args: x y / count=2

シェバンの書き方3種類の違い

#!/bin/bash と #!/usr/bin/env bash と #!/bin/sh

3つのシェバンを同じ内容のスクリプトで動かし、実際に読んだプログラムを表示しました。

$ cat shsh.sh
#!/bin/sh
echo "run by: $(ps -p $$ -o comm=)"
$ ./shsh.sh
run by: shsh.sh
$ readlink -f /bin/sh
/usr/bin/dash

$ cat envb.sh
#!/usr/bin/env bash
echo "run by: $(ps -p $$ -o comm=) $BASH_VERSION"
$ ./envb.sh
run by: bash 5.2.21(1)-release

#!/usr/bin/env bash は、env コマンドに「PATH から bash を探して起動して」と頼む書き方です。この環境では command -v bash が /usr/bin/bash を返し、そこにある bash 5.2.21 が使われました。

比べる点 #!/bin/bash #!/usr/bin/env bash #!/bin/sh
起動するもの /bin/bash 固定 PATH で最初に見つかる bash /bin/sh(Ubuntuでは dash)
bash の配列・[[ ]] 使える 使える 使えない(Syntax error)
bash が /bin 以外にある環境 見つからず止まる 動く 関係なし
利用者の PATH の影響 受けない 受ける(別の bash が選ばれることがある) 受けない
オプションを付ける #!/bin/bash -e と書ける そのままでは書けない(下記) #!/bin/sh -e と書ける
向いている用途 自分たちで管理する Linux サーバー 複数OSで共有するツール、配布するスクリプト 起動が軽く、どの環境にもある sh で足りる処理

迷ったときの目安はシンプルです。bash の機能を使うなら #!/bin/bash か #!/usr/bin/env bash、POSIX の範囲で書くなら #!/bin/sh を選びます。チームで使うスクリプトは、どちらかに統一しておくと読み手が迷いません。

env 経由でオプションを渡すときは -S を付ける

Linux では、シェバンのインタープリタ以降がまとめて1つの引数として渡されます。そのため #!/usr/bin/env bash -e と書くと、env は「bash -e」という名前のコマンドを探します。

$ cat envopt.sh
#!/usr/bin/env bash -e
echo hi
$ ./envopt.sh
/usr/bin/env: 'bash -e': No such file or directory
/usr/bin/env: use -[v]S to pass options in shebang lines
$ echo $?
127

エラーメッセージが案内しているとおり、-S を付けると env が空白で分割してくれます。

$ cat envs.sh
#!/usr/bin/env -S bash -e
echo hi
$ ./envs.sh
hi

-S は GNU coreutils の env の機能です(この環境の Ubuntu 24.04 で確認)。ほかのOSの env での動作は未実行です。持ち運びを優先するなら、シェバンは #!/usr/bin/env bash にとどめ、オプションは2行目に set -e のように書く方法がどの環境でも同じように働きます。

bash script.sh で実行するとシェバンのオプションは効かない

#!/bin/bash -e のようにシェバンへ付けたオプションは、./script.sh のときだけ有効です。bash script.sh ではシェバンごと読み飛ばされるため、オプションも無視されます。実行方法に左右されたくない設定は、スクリプト本文に set -eu などと書くのがおすすめです。

エラー文から引ける対処表

エラーが出たら、まずエラー文と終了コード(直後に echo $?)を確認します。126 は「見つかったが実行できない」、127 は「見つからない」という意味で使い分けられています。

エラー文(bash 5.2.21 で確認) 終了コード 原因 確認コマンド 対処
./hello.sh: Permission denied 126 実行権限(x)がない ls -l hello.sh chmod +x hello.sh、または bash hello.sh
./hello.sh: Permission denied(x は付いている) 126 noexec でマウントされた場所にある findmnt -T . bash hello.sh で実行、または別の場所に置く
hello.sh: command not found 127 ./ を付けていない、PATH にない echo $PATH ./hello.sh と打つ、または PATH の通った場所に置く
./nai.sh: No such file or directory 127 ファイル名か今いる場所が違う ls、pwd 正しいパスを指定する
./crlf.sh: cannot execute: required file not found 127 シェバンの行末に CR(改行コードが CRLF) file crlf.sh、cat -A crlf.sh sed -i 's/\r$//' crlf.sh
./badp.sh: cannot execute: required file not found 127 シェバンのパスにプログラムがない head -1 badp.sh、command -v bash シェバンを実在するパスに直す
/usr/bin/env: 'bash -e': No such file or directory 127 env 経由のシェバンにオプションを書いた head -1 script.sh #!/usr/bin/env -S bash -e、または本文に set -e
arr.sh: 2: Syntax error: "(" unexpected 2 bash 用スクリプトを sh(dash)で実行した ls -l /bin/sh bash arr.sh か ./arr.sh で実行
sh: 1: source: not found その行が127(次の行へ進む) sh には source がない 同上 . ./file.sh と書く
./d: Is a directory 126 ディレクトリを実行しようとした ls -ld d ファイル名を確認する
line 1: (見えない文字)#!/bin/bash: No such file or directory 0(続きは動く) ファイル先頭に BOM が付いている head -c 3 file | od -c エディタで「BOMなし UTF-8」で保存し直す

Permission denied(126):chmod +x で実行権限を付ける

もっともよく出会うのがこのエラーです。作成直後のファイルは通常 -rw-r--r--(644)で、x が付いていません。

$ ls -l hello.sh
-rw-r--r-- 1 taro taro 89 Oct  3 13:30 hello.sh
$ ./hello.sh
bash: line 1: ./hello.sh: Permission denied
$ chmod +x hello.sh
$ ls -l hello.sh
-rwxr-xr-x 1 taro taro 89 Oct  3 13:30 hello.sh

sh から呼んだ場合のメッセージも同じ意味です。

$ sh -c ./hello.sh
sh: 1: ./hello.sh: Permission denied
付け方 結果の例 意味
chmod +x file -rwxr-xr-x 所有者・グループ・その他すべてに実行権限
chmod u+x file -rwxr--r-- 所有者だけに実行権限
chmod 755 file -rwxr-xr-x 数値で指定(所有者は全部、ほかは読みと実行)
chmod 700 file -rwx------ 所有者だけが読み書き実行

表の4つは、644 のファイルに対して実行した結果の表示です。今回の検証は root ユーザーで行っており、root でも x が1つも付いていないファイルは Permission denied(126)になることを確認しました。一般ユーザーでの「所有者が違う」「親ディレクトリに入れない」といったケースは未実行ですが、その場合は ls -l で所有者を、ls -ld ディレクトリ で親ディレクトリの x を確かめる流れになります。

急いで動かしたいだけなら、権限を変えずに bash hello.sh で実行する方法もあります。

Git で管理している場合は、実行権限もリポジトリに記録できます。

$ git add run.sh
$ git ls-files -s
100644 68d8222fcba19a5bc88746843bba6cac1506517a 0   run.sh
$ git update-index --chmod=+x run.sh
$ git ls-files -s
100755 68d8222fcba19a5bc88746843bba6cac1506517a 0   run.sh

モードが 100644 から 100755 に変わりました(git 2.43.0)。これをコミットしておくと、clone した人の手元でも実行権限が付いた状態になります。Windows で作ったスクリプトを Linux で動かすときに役立ちます。

command not found(127):./ を付けるか PATH を通す

$ hello.sh
bash: line 1: hello.sh: command not found
$ ./nai.sh
bash: line 1: ./nai.sh: No such file or directory

メッセージの違いに注目してください。command not found は「PATH を探したが見つからない」、No such file or directory は「指定したパスにファイルがない」です。前者は ./ を付ければ解決し、後者はファイル名や現在のディレクトリを見直します。

bad interpreter / required file not found(127):CRLF とシェバンのパス

Windows のエディタで保存したスクリプトは、行末が CRLF(\r\n)になっていることがあります。するとシェバンが /bin/bash ではなく「/bin/bash+CR」という名前のプログラムを指すことになり、OS が見つけられません。

$ printf '#!/bin/bash\r\necho "crlf"\r\n' > crlf.sh; chmod +x crlf.sh
$ ./crlf.sh
bash: line 1: ./crlf.sh: cannot execute: required file not found
$ echo $?
127

同じファイルを zsh から実行すると、よく知られた「bad interpreter」の形で表示されました。

$ zsh -c './crlf.sh'
zsh:1: ./crlf.sh: bad interpreter: /bin/bash^M: no such file or directory

^M が CR を表しています。bash 5.2.21 では「cannot execute: required file not found」、zsh では「bad interpreter: /bin/bash^M」と、同じ原因でもシェルによって文言が違います。 以前の bash では /bin/bash^M: bad interpreter: No such file or directory と表示されることが広く知られていますが、この記事では以前の版は未実行です。dash から呼ぶと not found とだけ出ました。

$ dash -c './crlf.sh'
dash: 1: ./crlf.sh: not found

確認と修正の手順です。

$ file crlf.sh
crlf.sh: Bourne-Again shell script, ASCII text executable, with CRLF line terminators
$ head -1 crlf.sh | od -c | head -1
0000000   #   !   /   b   i   n   /   b   a   s   h  \r  \n
$ sed -i 's/\r$//' crlf.sh
$ ./crlf.sh
crlf

file コマンドが「with CRLF line terminators」と教えてくれます。cat -A でも行末に ^M$ が見えます。修正は sed -i 's/\r$//' で行末の CR を取り除くだけです(dos2unix コマンドでも同じことができますが、この記事では未実行です)。

CRLF のファイルを bash crlf.sh で動かすと、シェバンを使わないため1行だけの単純な内容なら動いてしまうことがあります。ただし if 文などを含むと別の形のエラーになります。

$ printf '#!/bin/bash\r\nNAME=taro\r\nif [ "$NAME" = "taro" ]; then\r\n  echo ok\r\nfi\r\ncd /tmp\r\n' > crlf2.sh
$ bash crlf2.sh
crlf2.sh: line 7: syntax error: unexpected end of file

then や fi の後ろに CR が付いて別の単語として扱われ、if が閉じられないまま終わったと判断されています。存在しないはずの行番号で「unexpected end of file」が出たら、改行コードを確認すると早く解決できます。

シェバンのパス自体が違う場合も、bash 5.2 では同じメッセージになります。

$ head -1 badp.sh
#!/usr/local/bin/bash
$ ./badp.sh
bash: line 1: ./badp.sh: cannot execute: required file not found

command -v bash で実際の場所を調べ、シェバンを合わせるか #!/usr/bin/env bash に変えます。

x があるのに Permission denied:noexec マウントを確認する

実行権限が付いているのに Permission denied になるときは、そのファイルシステムが noexec オプション付きでマウントされている可能性があります。tmpfs を noexec でマウントして再現しました。

$ mount -t tmpfs -o noexec tmpfs /tmp/t241ne
$ cp hello.sh /tmp/t241ne/ && chmod +x /tmp/t241ne/hello.sh
$ ls -l /tmp/t241ne/hello.sh
-rwxr-xr-x 1 root root 89 Oct  3 13:31 /tmp/t241ne/hello.sh
$ /tmp/t241ne/hello.sh
bash: line 1: /tmp/t241ne/hello.sh: Permission denied
$ echo $?
126
$ bash /tmp/t241ne/hello.sh
pid=975 shell=5.2.21(1)-release pwd=/tmp
$ findmnt -no TARGET,OPTIONS /tmp/t241ne
/tmp/t241ne rw,noexec,relatime

-rwxr-xr-x なのに直接実行は Permission denied、bash に読ませる形なら動きました。noexec は「その場所のファイルを直接プログラムとして起動させない」設定で、bash がファイルを読む動作は対象外だからです。

確認すること コマンド 見るところ
ファイルがある場所のマウント設定 findmnt -T /path/to/script.sh OPTIONS に noexec があるか
すぐ動かしたい bash /path/to/script.sh シェバンは使われない点に注意
恒久的に置く ホームディレクトリや /usr/local/bin など別の場所へ移す 管理者の方針に合わせる

USBメモリや共有フォルダ、/tmp が noexec になっている環境で見かけます。設定には理由があることが多いので、マウントオプションを変える前に管理者へ相談すると安心です。

目的別:どの実行方法を選ぶか

やりたいこと 選ぶ方法 理由
完成したスクリプトを日常的に使う chmod +x して ./script.sh シェバンどおりのシェルで動き、誰が実行しても同じ
cron や systemd から呼ぶ 絶対パスで /home/taro/bin/backup.sh、または /bin/bash /home/taro/bin/backup.sh PATH やカレントディレクトリに頼らない
もらったスクリプトをまず読んで試す bash -n で構文確認 → bash -x script.sh 権限を変えずに挙動を追える
環境変数や関数を今のシェルに入れる source file.sh または . ./file.sh 子プロセスでは呼び出し元に残らない
設定ファイル(.bashrc など)を反映する source ~/.bashrc 同上
POSIX sh 用のスクリプトを動かす ./script.sh(シェバンが #!/bin/sh)または sh script.sh dash は起動が軽い
作業ディレクトリを移動するスクリプトを作りたい 関数にして source で読み込む ./ で実行した cd は残らない

「スクリプトの中で cd したのに、終わったら元の場所にいる」という疑問は、4通りの比較で見たとおり子プロセスの cd は親に伝わらないことが理由です。移動を残したい場合は、次のように関数として定義したファイルを source で読み込みます。

# goto.sh(source で読み込んで使う)
gowork() {
  cd /tmp || return
  echo "moved to $(pwd)"
}
$ source goto.sh
$ gowork
moved to /tmp

関数は今のシェルの中で動くので、cd の結果がそのまま残ります。

新しいスクリプトを作るときのチェックリスト

順番 確認すること コマンド例
1 1行目にシェバンがある(先頭に空行や空白を入れない) head -1 script.sh
2 改行コードが LF になっている file script.sh
3 構文エラーがない bash -n script.sh
4 実行権限が付いている ls -l script.sh → chmod +x script.sh
5 シェバンと文法が合っている(bash の機能を使うなら bash) head -1 script.sh
6 Git に実行権限を記録した git ls-files -s script.sh で 100755
7 手順書の実行コマンドが統一されている(./ か bash か) README を確認

最小のひな形は次のとおりです。

#!/usr/bin/env bash
set -eu

main() {
  echo "Hello, ${1:-world}"
}

main "$@"
$ chmod +x template.sh
$ ./template.sh RJC
Hello, RJC
$ ./template.sh
Hello, world

処理を main 関数にまとめ、最後に main "$@" で呼ぶ形にしておくと、引数の受け渡しが1か所にまとまって読みやすくなります。

よくある質問

Q. シェバンを書かなくても動くのはなぜですか? A. bash script.sh や source script.sh はシェバンを使わないため、なくても動きます。./script.sh の場合も、シェバンがなければ呼び出したシェルが代わりに読みます(bash から呼べば bash、sh から呼べば sh が読むことを確認しました)。ただし呼び出し元によって読み手が変わるので、1行目に書いておくと挙動が安定します。

Q. sh script.sh と bash script.sh はどちらを使えばよいですか? A. スクリプトのシェバンに合わせます。#!/bin/bash や #!/usr/bin/env bash なら bash script.sh、#!/bin/sh なら sh script.sh です。Ubuntu では sh は dash なので、bash 用のスクリプトを sh で動かすと Syntax error: "(" unexpected のようなエラーになります。迷ったら chmod +x して ./script.sh で実行すれば、シェバンどおりのシェルが選ばれます。

Q. chmod +x と chmod 755 はどちらがよいですか? A. 新しく作った 644 のファイルに対しては、どちらも結果は -rwxr-xr-x(755)になります。+x は「今の権限に実行だけ足す」、755 は「この形に置き換える」という違いです。ほかの人に読ませたくないスクリプトは chmod 700 のように数値で指定すると意図がはっきりします。

Q. source と .(ドット)に違いはありますか? A. bash ではどちらも同じ組み込みコマンドです。. は POSIX で定められた書き方で dash などの sh でも使え、source は bash や zsh で使える読みやすい別名です。sh でも動かすファイルでは . ./file.sh と書いておくと、どのシェルでも通ります。

まとめ

  • ./script.sh は実行権限とシェバンを使う標準の方法。bash script.sh・sh script.sh は権限不要で、シェバンは使われない。
  • ./・bash・sh は子プロセスで動き、変数や cd は残らない。source(.)だけが今のシェルで動き、変数・cd・export が残る(pid が親と同じ 663 になることを確認)。
  • Ubuntu の /bin/sh は dash。bash 用スクリプトを sh で実行すると配列や [[ ]] で Syntax error になる。
  • シェバンは Linux サーバー向けなら #!/bin/bash、複数環境で使うなら #!/usr/bin/env bash、POSIX の範囲なら #!/bin/sh。env 経由でオプションを渡すときは -S を付ける。
  • Permission denied は 126、command not found は 127。ls -l で x を確認し、chmod +x か bash script.sh で対処する。
  • CRLF が原因のエラーは bash 5.2 では「cannot execute: required file not found」、zsh では「bad interpreter: /bin/bash^M」。file で確認し sed -i 's/\r$//' で直す。
  • x があるのに Permission denied なら noexec マウントを findmnt で確認する。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「シェルスクリプトの実行方法4通りの違いとシェバンの意味 エラー別対処つき」

株式会社RJCでは、シェルスクリプトの実行方法やシェバンの仕組みを仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?