de[v|b]log

ShellScript, Coffee, iOS/OSX Dev
Origin: Himajinworks.
About.

この記事は

Shell Script Advent Calendar 2016カッコつけないシェル関数定義 - Qiita を読んで、Shell関数の書き方の書き方に疎いことを知ったため、見直そうと思い調べ物をしたメモです。

私が以下を調べる きっかけになった記事・参考にした記事 を先に読むことをおすすめします

本当は12/21に書いたんだけど、どうせだったらいくつかコード修正してから出そうということで今になりました。

これまで

基本的に以下のような書き方をしていた。

1
2
3
function function-name() {
    ...
}

知っていた点としては、上記で言うところの function を省略できること。これは読んだQiitaの記事でも書かれていた。

ここで、「ある判定をしてその結果(終了コード)を返す関数」を書きたいとする。そのときこれまでは

1
2
3
4
function isValid() {
    [[ "$1" == 'text' ]]
    return $?
}

としていた。

記事を読んで知ったこと

ざっくりいうと

1
function function-name() function_body

ができるということ。この事自体と、関連して掘り下げた内容がすごい学びとなった。
ありがとうございます。すごい勉強になりました。(Shell周りだと今年トップ級かもです)

掘り下げてみる

参考にするのは The Open Group Base Specifications Issue 7 である。Qiitaの記事 では man にて記載されているもので解説されていました。

まず関数定義( 2.9.5 Function Definition Command )については、ざっくり書くと

1
function-name() compound-command

という形式になっており、 { ... } は書かないといけないということは書かれていない。
ここでこの { ... } について掘り下げる。
2.9.4 Compound Commands が含むものとして、

  • { ... }
  • ( ... )
  • for
  • case
  • if
  • while
  • until

が挙げられており、これらは上で書いた compound-command に該当する箇所に適用できるらしい。
これで カッコつけないシェル関数定義 - Qiita にて記載されている内容についてある程度理解ができた。

ここで次に、 記事 内にて compound-command に当たる箇所に echo をとっている箇所が気になった。
実際調べてみているが、これに関してはまだしっかり理解ができていない。 2.9.4 Compound Commands の項目には「複合コマンド(compound command)は予約語もしくはコントロールオペレータを最初に持ち、対応する語もしくはオペレータを末尾に持つ」とある。これより、この挙動はShellの実装依存であるということで理解している。
そうすると、Qiitaの 記事 にて書かれているように「 dashとzshではできてbashではうまくいかない 」というのの説明も理解できる。

ここまできて一点気になったのが、終了コードはどうなるかというところだった。
まずこれまで理解しきれていなかった点として、「関数の終了コードは最後に実行したコマンドの終了コード」という点がある。( 2.9.5 Function Definition CommandExit Status に記載されている。)
よって再掲となるが

1
2
3
4
function isValid() {
    [[ "$1" == 'text' ]]
    return $?
}

というコードを書いていたものが、

1
2
3
function isValid() {
    [[ "$1" == 'text' ]]
}

でよいということを理解した。
更に、 カッコつけないシェル関数定義 - QiitaThe Open Group Base Specifications Issue 7 から、

1
function isValid() [[ "$1" == 'text' ]]

と書いても(この書き方に対応しているShell上では)良いこととなる。
これが読みやすいか、適切かどうかについては別途議論の余地があるが、できることを知れたことは良いことだと思う。

上記3つの例は少なくともzsh 5.3上では同じ入力に対して同じ出力を返すことを確認した。

example.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#! /usr/bin/env zsh

function check1()
{
  [[ "$1" == 'text' ]]
  return $?
}

function check2()
{
  [[ "$1" == 'text' ]]
}

function check3() [[ "$1" == 'text' ]]

# Should return exit code 0
check1 text
echo $?

check2 text
echo $?

check3 text
echo $?

# Should return exit code 1
check1 invalid
echo $?

check2 invalid
echo $?

check3 invalid
echo $?

result

1
2
3
4
5
6
7
$ ./example.sh
0
0
0
1
1
1

まとめ

  • function 定義の細かい理解できていなかった点をある程度解決できた
    • 関数定義と複合コマンド、返り値の関連についてある程度理解できた
  • これまで $? で終了コードを返す実装をしていたものを修正していきたい

参考