解説
関数を抜けたいだけなのに処理が止まる?Bashのreturnとexitの範囲を知ろう
関数を終えるreturnと、シェルを終えるexitを子Bashで比較。呼び出し後の表示、終了コード、親へ戻る範囲を安全な例で確認。

シェルの関数でエラーを返したいだけなのに、その後の処理まで止まってしまった。Bashのreturnとexitは、どちらも数字を返せるけれど、終える範囲が違うよ。
このページでは、関数を「名前を付けてまとめた処理」として使い、二つの違いを比べる。Bashのターミナルが必要だよ。exitで普段使うターミナルを閉じないよう、例は新しく起動する子Bashの中だけで実行する。
returnは関数から戻る
bash --noprofile --norc -c '
task() {
printf "return:inside\n"
return 7
printf "return:unreachable\n"
}
task
printf "return:after status=%s\n" "$?"
'
表示はreturn:insideとreturn:after status=7の二行になる。関数の中のreturnより後ろは実行されないが、呼び出し元へは戻るので、その後のprintfは実行されるんだ。
7はこの例で決めた失敗を表す数字だよ。0以外の終了状態を、呼び出し元でどう扱うかは別に決める必要がある。ここでは、表示して内容を観察している。
exitは実行しているシェルを終える
bash --noprofile --norc -c '
task() {
printf "exit:inside\n"
exit 7
}
task
printf "exit:after\n"
'
printf 'parent:child-status=%s\n' "$?"
子の表示はexit:insideだけ。exitは関数を抜けて呼び出し元へ戻るのではなく、ここで動いている子Bash自体を終える。そのため、子のexit:afterは表示されないよ。
一方、子Bashを起動した親のターミナルには制御が戻り、parent:child-status=7が表示される。普段のターミナルで直接exit 7と入力する例とは違うんだ。
数字を表示する前に読む理由
$?には、直前に実行したコマンドの終了状態が入る。間に別のコマンドを挟むと、そのコマンドの結果へ変わるので、必要ならすぐ変数へ保存しよう。
task
status=$?
printf 'status=%s\n' "$status"
これは書き方を示す断片なので、先ほど定義した関数と同じシェル内で使う必要があるよ。子Bashの中だけに定義したtaskが、親にも自動で定義されるわけではない。
この例の前提と、実作業の確認
例の子Bashではset -eを有効にしていない。失敗時にシェルを止める設定や、&&・||などの条件との組み合わせでは、呼び出した後の流れにも別の規則が加わる。returnなら後続が必ず動く、という一般化はしないでね。
読み込んだスクリプトで使うreturnなど、関数以外の用法もあるけれど、ここでは扱わない。まずは「関数から戻るのか、そのシェルを終えるのか」を言葉にしてから選ぼう。

みんなのコメント 0
感想や、試してみたことを聞かせてね。コメント・返信は承認後に公開されます。
最初のコメントをどうぞ。