シェルスクリプトで関数を定義する時、function
と付けても付けなくても
定義出来たりしますが、
その辺のしっかりとした定義について。
function foo or foo ()
Bashなどのシェルスクリプトで関数を定義するには
function foo {
...
}
の様にfunction
を前に付けて定義するか
foo () {
...
}
の様にfunction
を使わずに後ろに()
を付けて定義するのが通常です。
今まで作ってるスクリプト(Bash)では、grep
とかで探しやすい、ということもあって
前者の方をよく使っていました。
この辺の違いについて Stack Exchangeの回答の中に分かりやすくまとまってるものがありました。
bash - difference between “function foo() {}” and “foo() {}” - Unix & Linux Stack Exchange
6つの関数の形についてPOSIXサポートなものなのか、もしくはどのシェルにサポートされてるものなのか などが書かれています。
foo () any-command
この形はBorne Shellで使われてたものですがBashではサポートされてません。 Zshではサポートされていて
$ foo () echo hello
$ foo
hello
と出来ます。この際、()
の両側のスペースはあってもなくても定義できます。
Bashでは
$ foo() echo hello
bash: syntax error near unexpected token `echo'`
と定義しようとするとエラーが出ます。
普段Bashを使うので知らなかったですが Zshの様に使えるシェルもある様です。 が、1コマンドなので関数にする必要がある場面が限られそう。
foo () any-compound-command
any-compound-command
とは{ ...; }
や()
、またfor
文やif
文でコマンドリストを囲った形を
指します1。
最初に書いた例の後者がこれにあたります({ ...; }
)。
この形はPOSIXでサポートされた形でシェルスクリプトを書くならこれを使っておけばOKなもの。
{}
で囲う場合、Bashではワンライナーで書こうとする時は最初の{
の後にスペースが必要になります。
(Zshだと要らない。)
for
文とかを使えるのは知りませんでした。Bashでも
$ foo () for i in 1 2 3;do echo $i;done
$ foo
1
2
3
$ foo () if [ 1 = 1 ];then echo hello;fi
$ foo
hello
$
みたいな事が出来ます。
function foo { …; }
最初に書いた例。 これはKorn shell (Ksh)で導入されたものでBashでも取り入れられていて、 これも多くのシェルでサポートされています。
ただしPOSIX準拠ではありません。
function foo () { …; }
前の2つの合わせ技的なもの。 なんとなくフルにこんな風に書いておく方が良い感じもしないでもないのですが、 この形は使うべきではないと言われています。
現在のBashやZshではこの形でもエラー無く使えますが
supported by accident
だそうで
将来的に使えなくなる可能性もありfunciton
を使わずfoo ()
の形を使った方が良い、とのこと2。
ただ、可読性を考えた時に 一般的な言語の関数の形を考えるとこの形に近いものが多いので この形がきちんとサポートされているのであれば使うのもありかも。
function foo () other-compound-command
{}
以外の()
などのcompound commandに関してはやはり現状BashやZshでは使えますが、
前の形が使えるKshでも使えないので、まず使うべきではない、と。
- function foo () simple command
この形はZshでのみ使えます。 最初の例と同じものですが、 やはり積極的に使うべきではないものです。
まとめ
関数の定義として、まずZshとかでは{}
で囲わない形でも
定義できる、ということをはじめて知りました。
また、()
やfor
文なんかでもそのまま定義出来るということも知りませんでした。
この辺ワンライナーを極めて行く上では上手く使えるとより簡潔に書けたりするかもしれません。
通常のシェルスクリプトでは{}
を使ってきちんと定義した方が見やすくもなるので
そうした方が良いとは思いますが。
形としてはfoo ()
のfunciton
を使わない形がPOSIX準拠で
とりあえずこれを使っておけば良い、という感じです。
特に#!/bin/sh
を使う必要があったり多環境で使う可能性のあるスクリプトでは
この形にすべきです。
Shellcheck等でもシェバンが#!/bin/sh
だったりシェバン無いスクリプトでは
SC2113: 'function' keyword is non-standard. Use 'foo()' instead of 'function foo'.
というエラーが出る様になってます。 (bash/zsh等がシェバンに指定されてる場合は出ません。)
funciton
を使うと関数を探しやすくなり可読性が上がる、ということはあるので
BashやZshでのスクリプトであればこれらを使う事も可能です。
個人で使う様なスクリプトに関しては好みで良いとは思いますが、 それぞれどの様なシェル/環境で使えるかということをきちんと把握しておく必要はあります。
個人的には、シェルスクリプトでは()
は
この中に引数の定義とかを書くわけでも無いので完全に飾り物で、
そうであればfunction
の名前を付けたほうが関数を見つけやすくて良いかな、
とも思ってました。
ただ、() {
とかの形も関数以外ではめったに使わないので検索とかでは
それ程無理せず見る事が出来ると言えば出来ます。
過去のスクリプトを見ても時々によって使ってるものが違いますが、
.bashrcを見てみたらfunction foo () {...
の形を使ってたので
整理も兼ねてfoo () {...
の形にしておきました。
追記: 2018/01/30
1つfunction
を使わないと出来ない事がありました。
それはaliasで定義された文字列と同じ名前の関数を作るときです。
$ alias a="echo alias"
$ a () { echo func; }
bash: syntax error near unexpected token `('
$ function a { echo func; }
$ a
alias
$ \a
func
$
こんな感じでa
というaliasを作った後にa ()
を定義しようとすると
a
がコマンドと解釈されその後の()...
の部分が引数的な扱いを受け
エラーが出ている様です。
一方、function
を使うと既にaliasになっていても定義できます。
aliasと関数が両方定義されてる場合にはaliasが優先されるのでそのまま 実行すればaliasの値が実行されますが、 aliasを無視する様にバックスラッシュでエスケープしてあげると 関数が実行されます。
Bash(4.4.12)とZsh(5.4.2)両方で同じ動作をすることを確かめました。
通常両方同時に定義する、ということは無いと思いますが、 ちょっと手元で問題になったのは.bashrcの中で ある環境ではaliasを定義、他の環境では関数として定義 する様なコマンドについて。
これがaliasとして定義される環境で、一度.bashrcを読み込んでから 再度手読み込むとこの関数の所でエラーが出ます。
具体的には以下の様なもの。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
Macとかだとtac
はaliasになり、2度目以降.bashrcを読み込んでも
aliasも関数の部分も使われません。
ですが、スクリプトを読み込む時の構文チェック的なものがあり
通らないパスにも関わらずtac ()
が評価され、aliasがあるのに
関数を定義しようとしてエラーが出ます。
ターミナル立ち上げ時には定義されてないので問題ありませんが 再度読もうとするとエラーが出てしまいます。
仕方ないのでこの部分に関してはfunction tac ()
としてあります。
最後の括弧無しの方が良い形かもしれませんが、
.bashrc内の全ての関数をfunc ()
の形にしたため、
検索でひっかけやすくするために括弧は残した形にしています。
一応この様な違いがある、ということでちょっと注意。
追記ここまで