関数と引数
よく使うひとまとまりの処理は関数にして名前を付けられます。name() { ... } と定義し、name 引数 と呼び出します。スクリプトや関数に渡された引数は $1 $2 のように番号で受け取り、$# で個数、$@ で全部をまとめて参照できます。関数の戻り値は return で終了ステータス(0〜255の整数)として返し、文字列を返したいときは echo で出力して呼び出し側で受け取ります。
スクリプトが長くなってくると、同じようなひとまとまりの処理が何度も登場するようになります。そのたびに同じ行を書き写すのは無駄が多く、後で直すときも修正漏れが起きがちです。そこで、よく使う処理のまとまりに名前を付けて、必要なときに名前を呼ぶだけで実行できるようにする仕組みが関数です。関数を使うと、スクリプトは「意味のある処理単位」の集まりとして整理され、読みやすく、直しやすく、再利用しやすくなります。さらに、外から値を受け取る引数の仕組みと組み合わせると、同じ関数を少しずつ違う対象に対して使い回せるようになります。
関数の定義と呼び出し
関数は 名前() { 処理 } という形で定義します。たとえば greet() { echo "こんにちは"; } と書けば greet という関数ができ、以降はスクリプトの中で greet と書くだけでその中身が実行されます。function greet { ... } という別の書き方もありますが、どちらでもかまいません。ここで大切なルールが一つあります。関数は、定義した行よりも後ろでしか呼び出せません。bash はスクリプトを上から順に読むため、まだ定義に出会っていない関数を先に呼ぶと「コマンドが見つからない」というエラーになります。そのため、関数の定義はスクリプトの前半にまとめて書き、実際の処理(メインの流れ)はその後ろに置く、という構成が定番です。
引数を番号で受け取る
関数やスクリプトに外から値を渡せると、汎用的に使えるようになります。スクリプトを ./script.sh apple banana のように実行したとき、後ろに付けた apple や banana が引数で、スクリプトの中では位置パラメータという仕組みで受け取ります。$1 が1番目の引数、$2 が2番目、というように番号で参照し、$0 はスクリプト名そのものを指します。関数の中でも同じ記号が使え、greet 太郎 のように呼べば、関数内の $1 は「太郎」になります。ここで注意したいのは、関数内の $1 は「その関数に渡した引数」であって、スクリプト自体に渡した引数とは別物だという点です。関数を呼ぶときに渡した値が、その関数の中の $1、$2 になります。
$# と $@ で引数全体を扱う
個々の引数だけでなく、引数の全体をまとめて扱う記号もあります。$# は渡された引数の個数を表し、$@ はすべての引数をまとめて参照します。$# は入力チェックでよく使われ、たとえば if [ "$#" -lt 2 ]; then echo "引数が足りません"; exit 1; fi と書けば、引数が2個未満のときにメッセージを出して終了する、という安全策を入れられます。$@ はすべての引数を順に処理したいときに for arg in "$@"; do echo "$arg"; done のように使います。$@ とよく似た $* もありますが、$@ がダブルクオートで囲んだとき各引数を個別の値として保つのに対し、$* は全体を1つの値にまとめてしまうという違いがあります。引数を1つずつ正しく扱いたいときは "$@" を使うのが安全です。なお shift というコマンドを使うと、位置パラメータを1つずつ前へずらせます($2 が新たな $1 になる)。引数を先頭から順番に消費しながら処理したいときに役立ちます。
return で終了ステータスを返す
関数の処理結果を呼び出し側に伝える方法は2通りあり、まず混同しやすい点を押さえましょう。return は、関数の終了ステータス(0〜255の整数)を返すための命令です。これは「計算結果の数値そのもの」を持ち帰るものではなく、あくまで成功(0)か失敗(0以外)かを示す合図です。たとえば is_even() { if [ $(( $1 % 2 )) -eq 0 ]; then return 0; else return 1; fi } という関数は、引数が偶数なら成功(0)、奇数なら失敗(1)を返します。呼び出し側は is_even 4 && echo "偶数です" のように、&& や $? を使ってこの成否を受け取れます。% は割り算の余りを求める算術演算子で、2で割った余りが0なら偶数、という判定です。
文字列や数値を持ち帰る方法
では、計算した数値や組み立てた文字列そのものを関数から持ち帰りたいときはどうするか。その場合は return ではなく echo で出力し、呼び出し側でコマンド置換を使って受け取ります。たとえば add() { echo $(( $1 + $2 )); } という関数を作り、result=$(add 3 5) と呼べば、変数 result に8という結果が入ります。関数の中で echo した内容が標準出力に流れ、それを $( ) が文字列として拾う、という流れです。「成功・失敗の合図は return、値そのものは echo して $( ) で受ける」と整理しておくと、両者を取り違えずに済みます。関連して、スクリプト全体を終了させて終了ステータスを返したいときは exit を使います。return が関数だけを抜けるのに対し、exit はスクリプトごと終了させる点が違います。関数の中で誤って exit を使うと、関数だけでなくスクリプト全体が止まってしまうので、「関数を抜けたいだけなら return、スクリプトを終わらせたいなら exit」と意識して使い分けてください。
関数内の変数は local で閉じ込める
関数を使ううえで見落としやすい落とし穴が、変数の有効範囲です。bash では、関数の中で普通に name=値 と代入すると、その変数はスクリプト全体で共有される(グローバルな)変数になります。つまり関数の中で書き換えた値が、関数を抜けた後の本体側にも影響してしまうのです。同じ名前の変数を関数の内と外でうっかり使っていると、互いに値を踏みつけ合って原因の分かりにくい不具合になります。これを避けるには、関数の中だけで使う変数に local を付け、local count=0 のように宣言します。local を付けた変数はその関数の中に閉じ込められ、外側には影響しません。関数を部品として安心して使い回すために、「関数の中で新しく使う作業用の変数には local を付ける」を習慣にしておくと、思わぬ値の混線を防げます。