多くのBashスクリプトは、厳密に命令型のスタイルで書かれています。コマンドを1つ実行し、次に別のコマンドを実行し、といった具合です。 ときに、概念上ひとつの目的を果たす一連のコマンドをひとまとめにしたいことがあります。 そんなときに役立つのが_関数_です。
関数は次のように定義します。
my_function () {
COMMANDS
}
空の括弧(())は、関数を定義していることを示すだけのものです。
その中には何も入れません。
関数は、一度定義すれば、他のあらゆるコマンド(組み込みコマンドかどうかに関わらず)と同じように動作します。
他のコマンドと同じように、関数にも_入力_を渡すことができます。
関数の内部では、位置パラメーター($1、$2など)を使って入力にアクセスします。
(変数のコンセプトで位置パラメーターについて学びましたね。)
特別なパラメーター$0は関数の中でも変わりません。実行中のスクリプトの名前のままです。
現在実行中の関数は、$FUNCNAME変数を使って自分の名前を取得できます。
マニュアルの3.4.2 特別なパラメーターを参照してください。
関数の中でも変数を定義できます。
localコマンドで変数を宣言すると、その変数の_スコープ_は現在の関数(およびそこから呼び出される関数)に限定されます。
そうでなければ、変数は_グローバルスコープ_に置かれます。
ローカル変数には、グローバル変数と同じ名前を付けることができます。 その場合、ローカル変数はグローバル変数を「覆い隠す」ことになります。 たとえば、関数内で宣言されたローカル変数は、同じ名前のグローバル変数を隠します。参照も代入もローカル変数を指すようになり、グローバル変数は変更されないままです。 関数から戻ると、グローバル変数が再び見えるようになります。
x=5
myfunc () {
local x=100
echo "in my function, $x == 100"
}
echo "in the global scope, $x == 5"
myfunc
echo "back in the global scope, $x == 5"
これは次のように出力されます。
in the global scope, 5 == 5
in my function, 100 == 100
back in the global scope, 5 == 5
関数の中からは、_呼び出し元_のスコープにある変数にアクセスできます。 つまり、グローバル変数だけでなく、呼び出し元(または呼び出し元を呼び出した何らかの関数)で宣言されたローカル変数も使えるということです。
厳密には、「グローバル」という言葉は適切ではありません。 関数内で変数を展開するとき、Bashはその変数名が宣言されている関数を探すために、コールスタックをグローバルスコープまでさかのぼります。
次の例は、マニュアルのシェル関数のセクションを基にしたものです。
func1() {
local var='func1 local'
func2
}
func2() {
echo "In func2, var = $var"
}
var=global
func1
func2
出力は次のとおりです。
In func2, var = func1 local
In func2, var = global
同じように、変数に値を_代入_すると、その値は_その変数が宣言されたスコープで_代入されることになります。 この「遠隔作用」は、どこで変数に値が代入されたのかが常に明白とは限らないため、追いにくいコードを生むことがあります。
コールスタックは、FUNCNAME配列変数を使って調べることができます。
関数は、他のコマンドと同じように、_終了ステータス_を持ちます。 既定では、関数のステータスは、_最後に実行されたコマンド_の終了ステータスになります。
returnコマンドを使うと、特定の終了ステータスで関数から戻ることができます。
check_password () {
if [[ $1 == "secret" ]]; then
return 0
else
return 1
fi
}
read -sp "Enter your password: " pass
if check_password "$pass"; then
echo "Correct!"
else
echo "Wrong password."
fi
returnを何も付けずに使うと、最後に実行されたコマンドのステータスを返します。
check_password関数は、次のように簡略化できることに注目してください。
check_password () { [[ $1 == "secret" ]]; }
[[...]]という条件構文には終了ステータスがあります。0は「真」、1は「偽」です。{...}というグループ化構文では、閉じ波括弧の前に改行かセミコロンのいずれかが必要です。関数の戻りステータスは、ただの数値にすぎません。 では、関数はどうやって出力を生み出すのでしょうか?
関数は標準出力に出力できます。 それを取り込むには、おなじみの_コマンド置換_を使います。
d6 () { echo "$(( 1 + RANDOM % 6 ))"; }
die=$( d6 )
echo "You rolled a $die."
関数の終了ステータスは、出力を取り込んでいるときでも利用できます。
roll () {
local n=$1
if (( 4 <= n && n <= 20 )); then
echo "$(( 1 + RANDOM % n ))" # exit status is 0
else
return 1
fi
}
read -p "How many faces does your die have? " faces
if die=$( roll "$faces" ); then
echo "You rolled a $die."
else
echo "I can't roll a die with $faces faces."
fi
関数は自分自身を再帰的に呼び出すことができます。 既定では、再帰の深さに制限はありません。
例を示します。
fibonacci() {
local n=$1
if (( n <= 1 )); then
echo "1"
else
local a=$(fibonacci "$(( n - 1 ))")
local b=$(fibonacci "$(( n - 2 ))")
echo "$(( a + b ))"
fi
}
for i in {1..10}; do fibonacci "$i"; done
# => 1
# => 2
# => 3
# => 5
# => 8
# => 13
# => 21
# => 34
# => 55
# => 89
再帰の深さは、FUNCNEST変数で制御できます。
bash -c '
recur() {
echo $1
recur $(($1 + 1))
}
FUNCNEST=5
recur 1
'
1
2
3
4
5
environment: line 1: recur: maximum function nesting level exceeded (5)