2013-12-09

Misc - Bash Advent Calendar - Day 8

今までちょっと説明が足りなかったところのランダムな補足。正直、既に息切れなので小休止。

配列変数の展開

特定の要素に対する文字列展開は、普通の変数の文字列展開と一緒なので省略。

${var[@]:-str}
var が存在し1個以上あれば ${var[@]} に同じ、そうでなければ str になる。var の中身を見て、それが空白文字だったら str に置きかわる、という動作をするわけではない。

${var[@]-str}
var が存在し1個以上あれば ${var[@]} に同じ、そうでなければ str になる。中身がない空の配列でも str に置き代わってしまうという残念な動作。仕様としては未定義っぽいけど。

${var[@]:?str}, ${var[@]:+word}
上と同様。

${var[@]:=str}
エラーになる。正直、エラーにならない場合、どんな動作をすべきか思いつかないので、妥当な仕様だと思う。

${var[@]:offset}, ${var[@]:offset:length}
offset 番目から length 個の要素を取り出す。実は同様に ${@:offset} とか ${@:offset:length} で、位置パラメータを部分的に取得できるんだけど、普通は shift 使えということで目にすることはまずないでしょう。残念ながら、各要素の部分文字列展開をしたい場合には、for ループで回すしかない。

${#var[@]}
配列の要素数になる。「各要素の文字列の長さ」が返ってくるわけではない。残念だけど、じゃぁ、配列の要素数を返すのに他に適当な記法があるかといえば、たしかにこれがベストと言わざるを得ないでしょう。

${var[@]#str}, ${var[@]##str}, ${var[@]%str}, ${var[@]%%str}
それぞれの要素に対して、先頭または末尾にマッチする文字列が削除、その結果を返します。

${var[@]/pattern/str}, ${var[@]//pattern/str}
それぞれの要素に対して、パターンにマッチした文字列を置換して、その結果を返します。

${var[@]^pattern}, ${var[@]^^pattern}, ${var[@],pattern}, ${var[@],,pattern}
それぞれの要素に対して、大文字、小文字変換変換した結果を返します。

というわけで、

  • 「変数が存在するかどうか、または空文字列か」をチェックする系のは動作はするけど : の有無が影響しないのでつまらない。
  • 数値を返す、または数値を引数に取る系のは、各要素に対して何か操作をするのではなく、配列全体に対する動作になる
  • 文字列置換系は各要素に対しての操作になる

とまとめられるかと思います。

[[ X -gt Y ]] と [ X -gt Y ] の互換性

正直、undocumented なので言及するだけ時間の無駄な気がしますが、現状、[[ ]] の場合は X Y ともに算術式評価がされるようです。なので
$ X=2
$ Y=1
$ [[ X -gt Y ]]; echo $?
0
$ [ X -gt Y ]; echo $?
bash: [: X: 整数の式が予期されます
2
と非互換の動作をします。算術式評価であれば、ちゃんと
(( X > Y ))
と書く方法があるので、"-gt" と test と同じ名前を使うのであれば、やっぱり test と同じような動作をすべきなんじゃないかと思うわけです。

declare コマンドと変数宣言

さてここまでずっと変数の宣言として declare コマンドを使ってきましたが、何回か書いてあるとおり、これらは内部「コマンド」であって bash の文法の一部ではありません。キバヤシなら「な、何だってー−!」と驚くところです。

で、何がポイントかというと、あるコマンドの実行結果を変数に代入する場合に問題になります。

  • $?は最後のコマンドの実行結果を返す
  • declareコマンドが実行される前に $() のコマンド展開が行われる
ということで

$ declare shadow=$(cat /etc/shadow 2>/dev/null)
$ echo $?
0
$ unset; declare shadow
$ shadow=$(cat /etc/shadow 2>/dev/null)
$ echo $?
1
実際のところ、declare コマンドでこれが問題になるケースは稀だと思いますが、その兄弟的な local コマンドでありがちなミスとして
function foo(){
  local tmp=$(mktemp foo.XXXXXX)
  if [[ $? -ne 0 ]]; then
    ...
}
みたいなのがあると思います。mktemp コマンドが失敗しても local コマンドは成功してしまうので、この if 文の評価は常に偽になります。

というわけで
local var=value
と書くと短く書けるのはいいのですが、右辺にコマンド展開がある場合は
local var
value=$(...)
と書く癖を付けておきましょう。

あと、これを書く動機として

  • declare -i -A または declare -i -a で右辺が算術評価されない

というバグがあります。これは
declare -i -a foo
foo=("1 + 2")
のように宣言と代入を分けることで回避できます。これ自体で困ることはそうそうないとは思いますが、こういうバグを見ると「どうやったらこんなバグ生じさせられるんだよ」とか「こんなところでバグるソフトとか信頼性どうなのよ」とか、でもきっと中の人は大変なんだろうなとか、いろいろと空想できて楽しいと思います。

というわけで今日の結論。やっぱり配列とか連想配列とか使うんだったら他のLL使いましょうw

0 件のコメント:

prometheusのrate()関数の罠

 久しぶりのAdventカレンダー挑戦、うまくいく気がしません。 閑話休題。実のところ、rate()関数というよりは、サーバー側のmetric初期化問題です。 さて、何らかのサーバーAがあったとして、それが更に他のサーバーBにRPCを送っているとします。サーバーBの方でホワイトボ...