evalを使う
シェルスクリプトにポインタの概念はありませんし
参照渡しとかも存在しませんが、
eval
を使うと文字列を一度解釈した上で式を実行してくれるので
それっぽいことが出来ます。
1 2 3 4 5 6 7 8 9 10 11 12 |
|
こんな感じ。
$ ./pass_pointer.sh
foo=1
pass_pointer: name=foo, value=1
foo=2
てな具合にfoo
という変数をpass_pointer
関数の中でいじって
外でも変更を確認出来ました。
シェルスクリプトだと普通に変数を宣言するとグローバル変数なので それをそのまま関数の中でいじることで変更することも多いですが、 汎用関数として使いたいような場合にはこんな感じでやるのもありかと。
もちろんこの引数で渡す変数もグローバル変数でないといけないので ちょっと注意。
グローバル変数で直接変更する
同じようなことを特別なことをせつにやりたい場合には 変更する変数をグローバル変数にして関数内では固定してしまうことです。
1 2 3 4 5 6 7 8 9 10 11 |
|
これも
$ ./fixed_name.sh
foo=1
fixed_name: name=foo, value=1
foo=2
となります。
foo
しか変更できなくなりますが、
シェルスクリプトなんでこんな感じで使う方が一般的だと思います。
echoで返す
変更する変数の名前を固定したくない時、
通常はecho
とかで値を返してfoo
に代入する、という形を使います。
1 2 3 4 5 6 7 8 9 10 11 12 |
|
これで
$ ./echo_value
foo=1
foo=echo_value: value=1
2
これで一応同じ様にfoo
の値を関数内で参照して外側でfoo
の値を変更出来ています。
関数内でfoo
という名前が見えないのはそれほど問題ないと思います。
ただ、echo
で情報を表示しようとするとそれが変数に入ってしまうので
無理やりエラー出力にして表示させたりしないといけないのがちょっと面倒なところです。
echo "echo_value: value=$var" > /dev/tty
の様に直接ターミナルに出力する方法もありますが、これだとスクリプト全体をログに書き出したい時とかに どうしようもなくなるのであまり使い勝手は良くないです。
まあ、こちらの方が素直かもしれませんが、もう一つの問題として、 こちらはサブシェルの呼び出しになるのでちょっと遅くなります。
まあ通常はほとんど気にしない所かもしれませんが、実際に測ってみると、
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 35 36 37 38 39 40 |
|
とかして1万回のループを試してみると
loop_time
real 0m0.037s
user 0m0.033s
sys 0m0.001s
pass_pointer
real 0m0.386s
user 0m0.341s
sys 0m0.006s
fixed_name
real 0m0.221s
user 0m0.159s
sys 0m0.002s
echo_value
real 0m16.376s
user 0m4.584s
sys 0m7.376s
という感じ。
最初のloop_time
ものは単にループそのものの時間がわからないと比較も難しいときもあるので一応出してますが、
今回は十分に小さいです。
fixed_name
が一番余計なことをしないので一番速いです。
pass_pointer
はfixed_name
の倍くらい。
そしてecho_value
の方はpass_pointer
の30倍位時間がかかっています。
1万回ループ回すことはそうそう々ないかもしれませんが、 千回位ならたまにはあるかと思います。 そういった時に1秒かかるかかからないか、は結構大きいかと。
ただecho_value
の方が素直ですしシェルスクリプトで関数から返り値をもらう、
といったらこの方法が普通なので一回きりの実行で
特に標準出力に別途出力する必要がないときなどはこの方法でやれば良いと思います。
returnで渡す
これは0以上の整数を扱う時限定になりますが
シェルスクリプトの関数はreturn
で終了値として数値を返せるのでそれを使う方法。
1 2 3 4 5 6 7 8 9 10 11 |
|
これで
$ ./return_value
foo=1
foo=return_value: value=1
2
と、echo_value
と全く同じことが出来ます。
しかも今度は関数内でecho
を標準出力で普通に使うことが出来るようになっています。
時間を測ってみると、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
これを使って
$ ./time_check_return_value.sh
loop_time
real 0m0.037s
user 0m0.035s
sys 0m0.000s
return_value
real 0m0.237s
user 0m0.233s
sys 0m0.001s
という感じでpass_pointer
よりむしろ速いです。
(試行回数を10倍にして何度か試しましたが、有意に1.5倍位pass_pointer
のが時間がかかります。)
シェルスクリプトのreturn
をこういった使い方をするのが正しいのかどうか知りませんが、
他の言語的な考えであれば当たりまえの使い方ですし、
正の整数に限るところではありますがこういった使い方もありかと。
まとめ
シェルスクリプト関数でポインタ渡しもどきを実装するという話から、 ちょっとシェルスクリプトの実行時間についての話にもなりました。
ポインタ渡しもどきに関してはこんな感じでそれっぽいことが出来ます。
echo
で返すやり方は一般的で通常はこれで良いと思いますが、
遅くなるし中でecho
が使えなくなるしあまりうれしいものじゃないです。
ただ、eval
自体はなんでも実行できてしまうので
時々脆弱性の話も出てきますし大事な所では使うのは避けたいかもしれません。
それぞれ
- pass_pointer:
- Pros: 速い。
- Cons: 変数はグローバル変数。
eval
が脆弱性を生む可能性がある。
- fixed_name:
- Pros: 最も速い。一般的な書き方。
- Cons: 変数はグローバル変数で一つに固定。
- echo_value:
- Pros: 変数がグローバル変数でなくても良い。一般的な書き方。
- Cons: 遅い。標準出力を普通に使えない。
- return_value:
- Pros: 速い。変数がグローバル変数でなくても良い。
- Cons: 0以上の整数しか使えない。ステータスチェックが出来なくなる。
と言った感じ。
通常はfixed_name
な方法とecho_value
な方法で作れば良いと思いますが、
他の方法も覚えておくと高速化出来たり便利に使えたりすると思います。
まあ、大事な所を操作したり速くしたいとかいうのであれば そもそも論シェルスクリプト使うな、って話なんですが。