🐧 Linux 総合学習プラットフォーム
シェルスクリプト ・ 中級

関数と引数

よく使うひとまとまりの処理は関数にして名前を付けられます。name() { ... } と定義し、name 引数 と呼び出します。スクリプトや関数に渡された引数は $1 $2 のように番号で受け取り、$# で個数、$@ で全部をまとめて参照できます。関数の戻り値は return で終了ステータス(0〜255の整数)として返し、文字列を返したいときは echo で出力して呼び出し側で受け取ります。

関数は、よく使う処理のまとまりに名前を付けて、名前を呼ぶだけで実行できるようにする仕組みだ。

スクリプトが長くなってくると、同じようなひとまとまりの処理が何度も登場するようになる。そのたびに同じ行を書き写すのは無駄が多く、後で直すときも修正漏れが起きがちだ。一か所直せば済むはずのことを、何か所も探して直すはめになる。

💡
ポイント関数は、処理のまとまりに名前を付けて呼べるようにする仕組み。一か所直せば全体に反映でき、読みやすく直しやすくなる。

関数を使うと、スクリプトは「意味のある処理単位」の集まりとして整理され、読みやすく、直しやすく、再利用しやすくなる。

さらに、外から値を受け取る引数の仕組みと組み合わせると、同じ関数を少しずつ違う対象に対して使い回せるようになる。

📐 関数は書く順番が大事

関数は 名前() { 処理 } という形で定義する。たとえば greet() { echo "こんにちは"; } と書けば greet という関数ができ、以降はスクリプトの中で greet と書くだけでその中身が実行される。function greet { ... } という別の書き方もあるが、どちらでもかまわない。

前半:関数の定義greet() { echo "こんにちは"; }後半:メインの流れ(ここで呼ぶ)greet

ここで見落とせないルールが一つある。関数は、定義した行よりも後ろでしか呼び出せないのだ。

つまずき関数は定義より後ろでしか呼べない。先に呼ぶと「コマンドが見つからない」になる。定義は前半にまとめて書く。

bash はスクリプトを上から順に読むため、まだ定義に出会っていない関数を先に呼ぶと「コマンドが見つからない」というエラーになる。

そのため、関数の定義はスクリプトの前半にまとめて書き、実際の処理(メインの流れ)はその後ろに置く、という構成が定番になる。

🔢 渡した値は番号で受け取る

関数やスクリプトに外から値を渡せると、汎用的に使えるようになる。スクリプトを ./script.sh apple banana のように実行したとき、後ろに付けた apple や banana が引数で、スクリプトの中では位置パラメータという仕組みで受け取る。

$1 が1番目の引数、$2 が2番目、というように番号で参照し、$0 はスクリプト名そのものを指す。関数の中でも同じ記号が使え、greet 太郎 のように呼べば、関数内の $1 は「太郎」になる。

./script.sh apple bananascript.shapplebanana$0$1$2

ここで混同しやすい点がある。関数内の $1 は「その関数に渡した引数」であって、スクリプト自体に渡した引数とは別物だという点だ。

つまずき関数内の $1 は「その関数に渡した引数」で、スクリプト自体の $1 とは別物。同じ $1 でも渡した相手で中身が変わる。

関数を呼ぶときに渡した値が、その関数の中の $1、$2 になる。同じ $1 でも、誰に向かって渡したかで中身が変わる、というわけだ。

🧮 引数の数や全体の扱い

個々の引数だけでなく、引数の全体をまとめて扱う記号もある。$# は渡された引数の個数を表し、$@ はすべての引数をまとめて参照する。

$# は入力チェックでよく使われ、たとえば if [ "$#" -lt 2 ]; then echo "引数が足りません"; exit 1; fi と書けば、引数が2個未満のときにメッセージを出して終了する、という安全策を入れられる。

if [ "$#" -lt 2 ]; then echo "引数が足りません"; exit 1; fi ……$# は引数の個数。2個未満なら知らせて終了する安全策。

$@ はすべての引数を順に処理したいときに for arg in "$@"; do echo "$arg"; done のように使う。

$@ とよく似た $* もあるが、両者には決定的な違いがある。$@ がダブルクオートで囲んだとき各引数を個別の値として保つのに対し、$* は全体を1つの値にまとめてしまうのだ。引数を1つずつ正しく扱いたいときは "$@" を使うのが安全だ。

コツ引数を1つずつ正しく扱いたいなら "$@"。"$*" は全体を1つの値にまとめてしまうので、個別に処理したいときは向かない。

なお shift というコマンドを使うと、位置パラメータを1つずつ前へずらせる($2 が新たな $1 になる)。引数を先頭から順番に消費しながら処理したいときに役立つ。

🎁 return が持ち帰るもの

関数の処理結果を呼び出し側に伝える方法は2通りあり、まず混同しやすい点を押さえよう。return は、関数の終了ステータス(0〜255の整数)を返すための命令だ。

ここで勘違いが起きやすい。これは「計算結果の数値そのもの」を持ち帰るものではなく、あくまで成功(0)か失敗(0以外)かを示す合図なのだ。

つまずきreturn が返すのは成功(0)か失敗(0以外)かの合図。計算結果の数値そのものを持ち帰るものではない。

たとえば is_even() { if [ $(( $1 % 2 )) -eq 0 ]; then return 0; else return 1; fi } という関数は、引数が偶数なら成功(0)、奇数なら失敗(1)を返す。

呼び出し側は is_even 4 && echo "偶数です" のように、&& や $? を使ってこの成否を受け取れる。前章で見た終了ステータスの考え方が、ここで関数の世界にも顔を出す。% は割り算の余りを求める算術演算子で、2で割った余りが0なら偶数、という判定だ。

📤 計算結果そのものは echo で返す

計算した数値や組み立てた文字列そのものを関数から持ち帰りたいときは、return ではなく echo で出力し、呼び出し側でコマンド置換を使って受け取る。

return成功・失敗の合図受け取り: && や $?echo値そのもの(数値・文字列)受け取り: result=$(関数)

たとえば add() { echo $(( $1 + $2 )); } という関数を作り、result=$(add 3 5) と呼べば、変数 result に8という結果が入る。関数の中で echo した内容が標準出力に流れ、それを $( ) が文字列として拾う、という流れだ。先に学んだコマンド置換が、ここで関数の戻り値を受け取る道具に化ける。

add() { echo $(( $1 + $2 )); } を result=$(add 3 5) と呼ぶ ……echo した8を $( ) が拾い、result に入る。
💡
ポイント成功・失敗の合図は return、値そのものは echo して $( ) で受ける。2つの持ち帰り方を取り違えないのがコツ。

「成功・失敗の合図は return、値そのものは echo して $( ) で受ける」と整理しておくと、両者を取り違えずに済む。

関連して、スクリプト全体を終了させて終了ステータスを返したいときは exit を使う。return が関数だけを抜けるのに対し、exit はスクリプトごと終了させる点が違う。関数の中で誤って exit を使うと、関数だけでなくスクリプト全体が止まってしまうので、「関数を抜けたいだけなら return、スクリプトを終わらせたいなら exit」と意識して使い分けてほしい。

🔒 関数の中の変数は外に漏れる

関数を使ううえで見落としやすい落とし穴が、変数の有効範囲だ。bash では、関数の中で普通に name=値 と代入すると、その変数はスクリプト全体で共有される(グローバルな)変数になる。

つまずき関数の中で普通に name=値 とすると、その変数はスクリプト全体で共有される。外の同名変数を踏みつぶす事故が起きる。

つまり関数の中で書き換えた値が、関数を抜けた後の本体側にも影響してしまうのだ。同じ名前の変数を関数の内と外でうっかり使っていると、互いに値を踏みつけ合って原因の分かりにくい不具合になる。

これを避けるには、関数の中だけで使う変数に local を付け、local count=0 のように宣言する。local を付けた変数はその関数の中に閉じ込められ、外側には影響しない。

コツ関数の中だけで使う作業用の変数には local を付ける(local count=0)。その関数に閉じ込められ、外を汚さない。

関数を部品として安心して使い回すために、「関数の中で新しく使う作業用の変数には local を付ける」を習慣にしておくと、思わぬ値の混線を防げる。

この項目に出てくる用語

関数かんすう
処理のまとまりに名前を付けて再利用できるようにしたもの。
位置パラメータいちぱらめーた
スクリプトや関数に渡された引数を表す $1 $2 … $@ $# のこと。

関連コマンド

echo

▶ 学習アプリでこの続きを学ぶ・演習する