ShellScript Tips --関数--
ShellScript の基本についての記事を, 備忘を兼ねて最近まとめています。
今回の記事では, 関数 について書きます。
なお, これまで明記してませんでしたが, 基本的には bash を前提 として書いています。
ShellScript の関数
ShellScript にも関数はあります。
ただし, 一般的なプログラミング言語の関数とは勝手が異なるので, その注意点を中心にメモしておきます。
基本: 関数定義と実行
まず関数を定義します。
def
などは不要で, 関数名(){コマンド}
の形で書いていきます (正確に言うと {}
である必要はなく, sh や bash の仕様で定義されている複合コマンドであれば何でもよいです, おまけで少し説明します)。
このあたりは他の言語と似ています。
#!/bin/bash func_countDown(){ for i in {5..1} do echo "${i}" done }
ちなみに {5..1}
は bash の ブレース展開*1 という機能を使っています*2。
{1..5}
だと 1 から 5, {a..z}
だと a から z などの連続した値を順に展開してくれる便利機能です。
ここでは脱線するので, 興味のある方は Man Bash(1)
の Brace Expansion などをご確認ください。
さて, この関数を実行するには, 初めに source
コマンドや .
コマンド*3を使って 現在実行中のシェルで関数定義を読み込む 必要があります。
./ファイル名
や bash ファイル名
のように実行すると, 現在のシェルと異なるプロセスで実行されるため, 現在のシェルには読み込んだ変数や関数が反映されないので, 実行できません。
実行時は, 他の多くの言語とは異なり ()
は不要で, 関数名 (と必要であれば引数) だけ指定すればよい です。
失敗例から見てみましょう。
[kangetsu@ubuntu16 shell_tips Sat Apr 04 17:07:37] $ bash func1.sh [kangetsu@ubuntu16 shell_tips Sat Apr 04 17:07:59] $ func_countDown func_countDown: command not found [kangetsu@ubuntu16 shell_tips Sat Apr 04 17:08:06]
現在のシェルに関数を読み込めていないので, command not found
になっています。
次は成功例です。
[kangetsu@ubuntu16 shell_tips Sat Apr 04 17:08:06] $ source func1.sh [kangetsu@ubuntu16 shell_tips Sat Apr 04 17:08:50] $ func_countDown 5 4 3 2 1 [kangetsu@ubuntu16 shell_tips Sat Apr 04 17:08:52]
source
で現在のシェルで関数定義を実行したことで, ちゃんと定義を読み込めており実行できています。
引数
上のサンプルは非常にシンプルなパターンでした。
今度は, 引数を受け取る関数を見てみます。
条件分岐について の記事のサンプルで実はすでに紹介していますが, ShellScript の関数で引数を利用したいときは, 特殊変数 $#
と $n
を使います。
$#
: 引数の数を格納する変数$n
: n番目の引数の値を格納する変数
#!/bin/bash func_countDown(){ if [ $# -gt 1 ] then echo "too much arguments!" return 1 else if [ $# -eq 1 ] then STR="$1" fi fi for i in {5..1} do echo "${i}" done if [ -n "${STR}" ] then echo "${STR}" fi }
.
で読み込んで, 引数の数を変えて実行してみます。
ちゃんと引数の数や内容に応じて処理が行われていることが分かります。
[kangetsu@ubuntu16 shell_tips Sat Apr 04 17:10:03] $ . func2.sh [kangetsu@ubuntu16 shell_tips Sat Apr 04 17:32:25] $ func_countDown 5 4 3 2 1 [kangetsu@ubuntu16 shell_tips Sat Apr 04 17:32:35] $ func_countDown hoge fuga too much arguments! [kangetsu@ubuntu16 shell_tips Sat Apr 04 17:32:46] $ func_countDown hoge 5 4 3 2 1 hoge [kangetsu@ubuntu16 shell_tips Sat Apr 04 17:32:49]
変数のスコープ:local によるローカル変数宣言
ところで, 上のサンプルでは関数 func_countDown
内で 変数 STR
を宣言しています。
他のプログラミング言語であれば, 大抵の場合これはローカル変数となり, スコープ外になれば参照できなくなります。
shell ではどうなるか, 先ほど関数を実行したものと同じシェルで STR
を参照してみます。
[kangetsu@ubuntu16 shell_tips Sat Apr 04 17:48:50] $ echo $STR hoge [kangetsu@ubuntu16 shell_tips Sat Apr 04 18:06:26]
参照できてしまいました。
このように, shell では基本的に変数はすべてグローバル変数 になります。
スコープを関数内に限定したい場合, bash のビルトインコマンド local
を使って変数を宣言する必要があります。
#!/bin/bash func_countDown(){ if [ $# -gt 1 ] then echo "too much arguments!" return 1 else if [ $# -eq 1 ] then local STR="$1" # ここで local を使ってローカル変数として宣言 fi fi for i in {5..1} do echo "${i}" done if [ -n "${STR}" ] then echo "${STR}" fi }
先ほどの実行結果の影響を受けないよう新しい bash
プロセスを立ち上げてから実行して確認してみます。
[kangetsu@ubuntu16 shell_tips Sat Apr 04 18:47:44] $ bash [kangetsu@ubuntu16 shell_tips Sat Apr 04 18:47:50] $ echo $STR [kangetsu@ubuntu16 shell_tips Sat Apr 04 18:47:53] $ . func4.sh [kangetsu@ubuntu16 shell_tips Sat Apr 04 18:47:59] $ func_countDown fuga 5 4 3 2 1 fuga [kangetsu@ubuntu16 shell_tips Sat Apr 04 18:48:10] $ echo $STR [kangetsu@ubuntu16 shell_tips Sat Apr 04 18:48:14]
このように, local
を使うことでローカル変数を宣言 できます。
戻り値
最後は戻り値についてです。
関数で処理をした結果を呼び出し元で受け取ってあれこれしたいことは多々あります。
これまでのサンプルでも何度か出ているように, bash には return
というビルトインコマンドがあります。
他の言語から連想して, return
に返したい値を渡せばよいのでは? と通常思うと思います。
では, 関数の最後で "SUCCESS!" という文字列を返すことを試してみます。
#!/bin/bash func_countDown(){ for i in {5..1} do echo "${i}" done return "SUCCESS!" }
[kangetsu@ubuntu16 shell_tips Sat Apr 04 20:18:32] $ . func5.sh [kangetsu@ubuntu16 shell_tips Sat Apr 04 20:18:42] $ func_countDown 5 4 3 2 1 -bash: return: SUCCESS!: numeric argument required [kangetsu@ubuntu16 shell_tips Sat Apr 04 20:18:49]
エラーとなりました。
エラーメッセージにあるように, bash の return
は数値のみを返せます。
Man bash(1)
に仕様があるので見てみます。
return [n] Causes a function to stop executing and return the value specified by n to its caller. If n is omitted, the return status is that of the last command executed in the function body. If return is used outside a function, but during execution of a script by the . (source) command, it causes the shell to stop executing that script and return either n or the exit status of the last command exe‐ cuted within the script as the exit status of the script. If n is supplied, the return value is its least significant 8 bits. The return status is non-zero if return is supplied a non-numeric argument, or is used outside a function and not during execution of a script by . or source. Any command associated with the RETURN trap is executed before execution resumes after the function or script.
- 関数の実行を止める (多くの他の言語と同様)
n
を渡していればn
を, 省略していれば直前のコマンドの exit code を返す
というところが基本で必要な点かと思います。
このように, bash の return
は exit code を返す目的で使われ, 数値以外を返すことはできません。
では, 他の言語のように数値以外を受け取りたい場合はどうすればよいか。
実は, 例えば関数の実行結果を変数に格納などすると, その関数の標準出力が格納されます。
例を見るのが分かりやすいと思うので, 試してみます。
[kangetsu@ubuntu16 shell_tips Sat Apr 04 20:59:59] $ RET=$(func_countDown) -bash: return: SUCCESS!: numeric argument required [kangetsu@ubuntu16 shell_tips Sat Apr 04 21:00:06] $ echo $RET 5 4 3 2 1 [kangetsu@ubuntu16 shell_tips Sat Apr 04 21:00:09]
$()
でコマンド (今回は関数) を実行し, その結果を変数 RET
に代入しています。
中身は, func_countDown
を実行した際の標準出力と同じ, 5 4 3 2 1
となりました。
return
の誤用を警告するエラーメッセージが格納されていないのは, これが標準出力でなく標準エラー出力だからです。
つまり, 関数実行時に任意の文字列などを受け取りたい場合, return
に渡すのではなく, 標準出力に渡してやればよいことになります。
先ほどのスクリプトを改修して, SUCCESS!
という文字列を受け取れるようにしてみましょう。
#!/bin/bash func_countDown(){ for i in {5..1} do echo "${i}" done echo "SUCCESS!" }
[kangetsu@ubuntu16 shell_tips Sat Apr 04 21:05:25] $ . func6.sh [kangetsu@ubuntu16 shell_tips Sat Apr 04 21:05:28] $ RET=$(func_countDown) [kangetsu@ubuntu16 shell_tips Sat Apr 04 21:05:29] $ echo $RET 5 4 3 2 1 SUCCESS! [kangetsu@ubuntu16 shell_tips Sat Apr 04 21:05:32]
確かに, echo
で文字列を標準出力に渡すことで, 変数 RET
に格納できました。
しかし, 不要な 5 4 3 2 1
まで一緒になっています。
これは, 5 4 3 2 1
も標準出力に出力されているためです。
これを回避し, "SUCCESS!" という文字列だけ受け取るには, 受け取りたくないもの以外は標準出力以外の場所に渡す必要があります。
最も分かりやすいのは次の例でしょう。
#!/bin/bash func_countDown(){ for i in {5..1} do echo "${i}" > /dev/null done echo "SUCCESS!" }
[kangetsu@ubuntu16 shell_tips Sat Apr 04 21:05:32] $ . func7.sh [kangetsu@ubuntu16 shell_tips Sat Apr 04 21:08:17] $ RET=$(func_countDown) [kangetsu@ubuntu16 shell_tips Sat Apr 04 21:08:21] $ echo $RET SUCCESS! [kangetsu@ubuntu16 shell_tips Sat Apr 04 21:08:24]
5 4 3 2 1
を出力する部分で, 標準出力でなく /dev/null
に捨てることでほしいものだけに絞ることができました。
ただし, このままでは for
ループの結果がどうなったのかどこにも出力されないので, 実際にこうした処理をする場合はちゃんと影響を考慮して, /dev/null
でないところに渡すなど, 工夫が必要かと思います。
まとめ
今回のまとめです。
ShellScript の関数は他の言語と似ている部分もありますが, 同じように扱おうとすると違いに悩まされます。
以下の点を把握しておけば, 最低限 ShellScript の関数を利用することはできるかと思います
- 他のファイルに定義した関数の呼び出し時は, source か . コマンドでそのファイルを読み込むと呼び出せるようになる
- 呼び出し時には関数名と引数だけでよく, () は不要
- bash の関数内の変数は全てグローバル変数
- ローカル変数を使いたいときは変数宣言時に local を使う
- bash 関数の戻り値は関数内の標準出力になる
- return は exit code を返すためのものなので注意
おまけ:bash における関数定義の仕様
Man bash(1)
には, bash における関数定義が書いてあります。
「基本: 関数定義と実行」の部分で少し触れましたが, これを読むと, 関数定義時の ()
の後は必ずしも {}
でなく, compound-command
であればよいということが書かれています。
Shell Function Definitions
A shell function is an object that is called like a simple command and executes a compound command with a new set of positional parameters.
Shell functions are declared as follows:
name () compound-command [redirection]
function name [()] compound-command [redirection]
This defines a function named name. The reserved word function is optional. If the function reserved word is supplied, the parenthe‐
ses are optional. The body of the function is the compound command compound-command (see Compound Commands above). That command is
usually a list of commands between { and }, but may be any command listed under Compound Commands above. compound-command is executed
whenever name is specified as the name of a simple command. When in posix mode, name may not be the name of one of the POSIX special
builtins. Any redirections (see REDIRECTION below) specified when a function is defined are performed when the function is executed.
The exit status of a function definition is zero unless a syntax error occurs or a readonly function with the same name already
exists. When executed, the exit status of a function is the exit status of the last command executed in the body. (See FUNCTIONS
below.)
つまり, 次のような記述でも問題ありません。
#!/bin/bash func_countDown2() (echo "hoge")
[kangetsu@ubuntu16 shell_tips Sat Apr 04 21:18:29] $ . func3.sh [kangetsu@ubuntu16 shell_tips Sat Apr 04 21:18:31] $ func_countDown2 hoge [kangetsu@ubuntu16 shell_tips Sat Apr 04 21:18:32]
とは言え積極的に他の記述法を使うこともないとは思うので, おまけでした。
- 作者:三宅 英明
- 発売日: 2017/11/21
- メディア: Kindle版