やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

bashで関数を書く

 記法、引数、戻り値、変数(local, local -r)。

成果物

構文

MyFunc() { echo F; }
MyFunc
意味 コード例
定義 MyFunc() { echo F; }
実行 MyFunc

 関数はfunctionを付与して定義するが省略できる。

function MyFunc() { echo F; }

引数

 $n(nは数値)で位置引数を取得する。

Sum() { echo $(($1 + $2)); }
echo $(Sum 10 20)

 $0スクリプトファイルパス。コマンドラインシェルのときはシェル実行ファイル名。

ParentDir="$(dirname $0)"

 $@はすべての引数。""で囲むとスペースも含まれたひとつの文字列になる。""で囲まないとスペース区切りの単語になる。

Msg() { echo "$@"; }
Msg "やあ" "hello world."
Msg() { for msg in $@; do echo $msg; done; }
Msg "やあ" "hello world."

 getoptsコマンドで引数解析できるが、複雑な解析はできない。

終了コード取得

MyFunc() { return 255; }
MyFunc
RET=$?
echo $RET

 関数は終了コードを設定できる。return(exit)の第一引数に0255の数値を渡すことで。0が正常、1255が異常。その他の値は不可。

 もしreturnに文字列を指定すると実行時エラーになる。

MyFunc() { return 'A'; }
MyFunc
bash: return: A: 数字の引数が必要です

stdout取得

MyFunc() { echo 'RESULT'; }
RET="$(MyFunc)"
echo $RET

 関数から文字列を受け取りたいなら、コマンド置換によりstdoutを取得する。

local変数

MyFunc() { local RET="RESULT"; echo "$RET"; }
RET="$(MyFunc)"
echo $RET

 関数内でのみ使用可。localを付与すれば関数内のみ参照できる変数を使える。グローバル変数を作らないので名前汚染を防げる。

 set -uで未割当時にエラーとすると確認できる。

set -u
MyFunc() { local RET="RESULT"; echo "$RET"; }
echo $RET
bash: RET: 未割り当ての変数です

 同名のグローバル変数とローカル変数が宣言されたときの話。関数内での参照は同名グローバル変数よりも同名ローカル変数が優先して参照される。

RET=GLOBAL
MyFunc() { local RET="RESULT"; echo "$RET"; }
RET="$(MyFunc)"
echo $RET
RET=GLOBAL
MyFunc() { echo "$RET"; }
RET="$(MyFunc)"
echo $RET

 同名ローカル変数があったとき同名グローバル変数を参照する方法はない。と思う。

local -r

 localかつreadonlyにする。

MyFunc() { local -r RET="RESULT"; RET="A"; echo "$RET"; }
RET="$(MyFunc)"
echo $RET

 再代入エラーが出る。

bash: RET: 読み取り専用の変数です

readonly

 グローバル変数のみ対象。

readonly RET="RESULT"
RET="A"

 再代入しようとすると以下のエラーになる。

bash: RET: 読み取り専用の変数です

 localとの併用はできない。ふつうに再代入されてしまう。

MyFunc() { local readonly RET="RESULT"; local readonly RET="A"; echo "$RET"; }
MyFunc() { local readonly RET="RESULT"; local RET="A"; echo "$RET"; }
MyFunc() { local readonly RET="RESULT"; RET="A"; echo "$RET"; }
RET="$(MyFunc)"
echo $RET

 local -rを使うべし。

1行データずつ関数を呼ぶ

 while readで1行ずつ処理する。

Msg() { echo "MSG: $1"; }
seq 1 3 | while read line; do { Msg ${line}; }; done;

 ワンライナーにすると以下。

Msg() { echo "MSG: $1"; }; seq 1 3 | while read line; do { Msg ${line}; }; done;

 もし1行データを累計したいなら以下。

Add() { echo "$(($1+1))"; }
seq 1 3 | ( SUM=0; while read line; do { SUM=$((SUM+$(Add $line))); }; done; echo $SUM )

 ワンライナーにすると以下。

Add() { echo "$(($1+1))"; }; seq 1 3 | ( SUM=0; while read line; do { SUM=$((SUM+$(Add $line))); }; done; echo $SUM );

xargsで関数を呼ぶ

 whileの代わりにxargsを使うと以下。

Msg() { echo "MSG: $1"; }
export -f Msg
seq 1 3 | xargs -I@ bash -c 'Msg @'

 ポイントはexport -f Msgxargs bash -c '...'だと別プロセスとなり関数が未定義になる。そこでシェル変数から環境変数へ出力してプロセス間共有させる。export -f 関数名で。

 ワンライナーにすると以下。

Msg() { echo "MSG: $1"; }; export -f Msg; seq 1 3 | xargs -I@ bash -c 'Msg @';

 集計はわからん。

対象環境

  • Raspbierry pi 3 Model B+
  • Raspbian stretch 9.0 2018-11-13
  • bash 4.4.12
$ uname -a
Linux raspberrypi 4.14.98-v7+ #1200 SMP Tue Feb 12 20:27:48 GMT 2019 armv7l GNU/Linux