関数の使用方法

関数とは?

ある一連の処理をまとめて一つの機能としたものを関数という。似たような処理を複数箇所で実行している場合は、その一連の処理を関数化して利用すると便利である。

# 関数の定義(※定義時は関数名に「( )」を付ける)
function 関数名() {
  処理
  return}
# 頭につける「function」は省略可
関数名() {
  処理
  return}
# 関数の呼び出し(※呼び出し時は「( )」は不要)
関数名 引数リスト

→ 関数名を指定したブロック内に処理を定義する。

関数名とその関数の処理を定義することで、一連の処理を関数を呼び出すことが可能になる。通常、関数の最後には return コマンドを指定し、その引数として与えた値が関数の終了ステータスとなる。

return コマンドの引数に指定できる値は、0 もしくは 1~255 の正の整数値のみである。マイナスの値も指定可能であるが、-1 を指定した場合は結果的に 255 になる。紛らわしいので、0 もしくは 1~255 の正の整数以外は指定しないようにする。

この return コマンドは省略可能で、省略された場合は関数内で最後に実行されたコマンドの終了ステータスが、関数自体の終了ステータスとなる。

関数を呼び出すときは、関数名と引数のリストを指定する。引数は省略可能である。呼び出すときの関数名は C言語などのように () を付ける必要はない。func() ではなく func のように、単純に関数名のみを指定する。

関数の戻り値

関数の戻り値としての標準出力

シェルスクリプトの関数には、いわゆる戻り値というものが存在しない。前述のとおり、return コマンドに指定した値は、関数の終了ステータスとなるだけで、一般的な戻り値としては機能しない。

これがシェルスクリプト初心者にはもっとも理解しづらい点に思える。シェルスクリプトの関数は、戻り値ではなく標準出力で結果を返す。まずはこの点を理解してほしい。

戻り値を使用したい場合は、以下のように関数内で標準出力に戻り値を出力し、コマンド置換でその値を取得する。

func() {
  echo "これが戻り値です。"
}
rtn=`func`

echo "戻り値=${rtn}"

→ 関数の戻り値は標準出力を使用し、コマンド置換で取得する。

関数の戻り値とグローバル変数

他にも関数内部でグローバル変数に戻り値を格納する方法もあるが、この方法だと使用する関数内部の処理を調査して、あらかじめ戻り値に使用される変数名を確認しておく必要がある。

そうなると可読性と使いやすさの面で問題が出るため、グローバル変数を戻り値の受け渡しに使用する方法は非推奨とする

一方で、コマンド置換で戻り値を取得する方法だと、戻り値に使用される変数名をあらかじめ確認する必要がない。モジュール化において、知らなくても使えるようにすることは正義である。

#戻り値設定用変数
rtn=""
func() {
  # 関数内で戻り値設定用変数に戻り値を設定する
  rtn="これが戻り値です。"
}
echo "戻り値=${rtn}"

→ 弊害が大きすぎるため、戻り値としてのグローバル変数の使用は非推奨!

関数の引数

関数は引数を持つことが可能で、関数内ではシェルスクリプトと同様に特殊変数 $1$n で各引数を参照することが可能である。

同じく $#$@$* も使用可能であるが、$0 のみは関数名とはならず、実行元シェルスクリプトの $0 の値が設定されたままになる。

以下、実際に関数内で特殊変数を使用してみる。

#!/bin/bash

func() {
  echo "\$0=$0"
  echo "\$#=$#"
  echo "\$@=$@"
  echo "\$*=$*"
  echo "\$1=$1"
  echo "\$2=$2"
  echo "\$3=$3"
}

func 001 002 003

上記シェルスクリプト (func_param.sh) の実行結果は、以下のとおりとなる。

$ ./func_param.sh
$0=./func_param.sh
$#=3
$@=001 002 003
$*=001 002 003
$1=001
$2=002
$3=003

$0 は関数内部でも関数名にはならないのが確認できる。

ローカル変数

一般的なプログラミング言語では、関数内で宣言した変数は、自動的に関数内 (スコープ内) でのみ使用可能なローカル変数となるが、シェルスクリプトでは関数内で宣言した (シェルスクリプトに変数の宣言はないので、正確には関数内で初めて使用した) 変数はグローバル変数となる。

$ func() {
> hoge="hoge hoge"
> echo "$hoge"
> }
#↑コマンドラインから関数を定義する。

$ func
hoge hoge
※↑定義した関数を呼び出し。
$ echo "$hoge"
hoge hoge
#↑関数内の変数が関数外でも有効になっている (グローバル変数となっている)。

そのまま変数を使用するとグローバル変数となるが、ローカル変数としたい変数に local を指定することで、その変数をローカル変数として扱うことができる

func() {
  local local_var="この変数はローカル変数です !"
  local readonly LOCAL_VAR="この変数はローカル定数です !"
}

→ 変数宣言時に local を指定する。

local キーワードを指定することで、対象となる変数は、関数内部でのみ使用可能なローカル変数となる。

以下、実際に local を指定して、ローカル変数を作成してみる。

#!/bin/bash

local_var="この変数はグローバル変数です !"
readonly LOCAL_VAR="この変数はグローバル定数です !"

LocalVar() {
  # まだローカル変数として宣言していないので,外部で宣言されたグローバル変数が有効
  echo "$local_var"
  echo "$LOCAL_VAR"

  # ローカル変数として宣言 (readonly のグローバル変数はローカル変数として再定義できない)
  local local_var="この変数はローカル変数です !"
  local readonly LOCAL_VAR="グローバル定数と同じ名前の定数は local として使用できません !"
  local readonly LOCAL_READONLY="この定数はローカル定数です !"

  echo ""

  # LOCAL_VAR 以外はローカル変数
  echo "$local_var"
  echo "$LOCAL_VAR"
  echo "$LOCAL_READONLY"
}

LocalVar

echo ""

# 関数を抜けたので,全てグローバル変数の値となる (関数内で local 宣言した同名変数に設定した値は破棄されている)
echo "$local_var"
echo "$LOCAL_VAR"
echo "$LOCAL_READONLY"

このシェルスクリプト (local_var.sh) の実行結果は、以下のとおりとなる。

$ ./local_var.sh
この変数はグローバル変数です !
この変数はグローバル定数です !
./local_var.sh: line 11: local: LOCAL_VAR: readonly variable

この変数はローカル変数です !
この変数はグローバル定数です !
この定数はローカル定数です !

この変数はグローバル変数です !
この変数はグローバル定数です !

上記実行例から、以下のことが確認できる。

  • 関数内の local 変数 $LOCAL_READONLY は関数外で参照できていない
  • 関数内で上書きされた同名の変数は関数外で書き換える前の値になっている
  • readonly 指定されたグローバル変数は local 指定でも再定義できない

関数の使用例

引数の合計値を出力する関数

関数の例として、引数に与えられた数の和を求め、標準出力に出力する関数 (sum_function.sh) を作成してみる。なお、この関数に数字以外の文字列を与えた場合は、空文字を出力し終了ステータス1で終了する。

sum()
{
  # 解説 1
  local total=0
  while [ $# -ne 0 ]
  do
    # 解説 2
    total=`expr $total + $1 2>/dev/null`

    # 解説 3
    case $? in
      0 | 1 ) shift ;; # 解説 3
          * ) echo ""; return 1 ;;
    esac
  done

  echo "$total"
  return 0
}

上記、関数内のコメントにある「解説 1」「解説 2」「解説 3」に関して、詳細は次のとおり。

sum_function.sh 解説 1

変数初期化時に local hoge="fuga" のように、local を指定することで、関数内でのみ参照可能なローカル変数として使用できる。

sum_function.sh 解説 2

expr コマンドの終了ステータスを判定する。expr コマンドは計算が成功すると、終了ステータスが 0 もしくは 1 になる。

つまり、終了ステータスがそれ以外の値であれば、引数に数字以外の文字列が指定されていたということになる。

expr コマンドの終了ステータスは以下の表のとおり。

計算結果 終了ステータス
0 以外 0
0 1
計算失敗 0, 1 以外

sum_function.sh 解説 3

引数のリストを shift する、つまり先頭の引数を消去し、2番目の引数以下を先頭に 1つずらす。

例えば「“aaa” “bbb” “ccc”」という引数リストが指定されていたならば、shift コマンドを実行すると引数リストは「“bbb” “ccc”」という状態になる。

このシェルスクリプト(関数) sum_function.sh の実行結果は、以下のとおりとなる。

$ . ./sum_function.sh
#↑ドットコマンド(.)でカレントシェルにsum()関数を読み込む。

$ sum 1 2 3 4 5 6 7 8 9 10
55
$ echo $?
0
#↑正常な引数だと合計値が出力され、終了ステータスは 0 となる。

$ sum 1 2 3 4 5 6 7 8 9 a

$ echo $?
1
#↑引数に数字以外の文字が混ざっていると、空文字が出力され終了ステータスは 1 となる。

ドットコマンド(.) を使用すると、指定したファイル内に記述されている変数や関数がカレントシェルに展開される。これにより指定したファイル内に定義されている関数や変数が使用可能となる。

この方法を使うと、頻繁に使用する機能をあらかじめ関数としてファイルに定義しておき、各シェルスクリプト内でそのファイルをドットコマンドで読み込んで使用する、といったように関数の再利用が容易になる。

次に関数をシェルスクリプト (call_sum_function.sh) 内で使用してみる。関数は使用するシェルスクリプト内で直接定義してもよいが、下記の例のように外部のファイルに定義しておき、ドットコマンドで読み込んで使用する方が、関数を再利用する際に便利である。

#!/bin/bash

# 関数をドットコマンドで読み込む(同一ディレクトリ内に存在するものとする)
. ./sum_function.sh

# 引数の数が 0 ならばエラー
if [ $# -eq 0 ]; then
  echo "引数を指定してください。" 1>&2
  exit 1
fi

# 関数を呼び出し
sum $@

# 関数の終了ステータスを判定する
if [ $? -eq 1 ]; then
  echo "引数は数字のみ有効です。" 1>&2
  exit 1
fi

exit 0

このシェルスクリプト call_sum_function.sh の実行結果は、以下のとおりとなる。

$ ./call_sum_function.sh
引数を指定してください。
$ ./call_sum_function.sh 10 20 30 AA 50
引数は数字のみ有効です。
$ ./call_sum_function.sh 10 20 30
60