Ansatzの備忘録

勉強したことあるいはふと思い立ったこと

シェルワンライナー160本ノック問題18

問題18 シェルのビルトインだけでの集計

/etc/passwd に書いてある各ログインシェルの個数を、シェルのビルトインだけで集計する問題だった。難しくてさっぱりわからなかった。

解答例は次の通り。

declare -A x ; IFS=: ; while read {a..g} ; do x[$g]+=. ;done < /etc/passwd ; for s in ${!x[@]}; do echo $s ${#x[$s]}; done ; unset x

for 文からがよくわからなかった。

 ${!name[@]}
 ${!name[*]}
配列のキーのリスト。 name が配列変数であれば、配列 name の インデックス (キー) のリストに展開されます。 name が配列でない場合は、name が設定されていれば 0 に、 そうでなければ空に展開されます。 ダブルクォートの中で @ が使われた場合、それぞれのキーは 別々の単語に展開されます。

上記のマニュアルを読むと、${!x[@]} で配列 x のキーのリストを作っているとわかった。そしてリストの各要素を s に for ループでセットしている。最後に配列を削除している理由は、削除しないとこのワンライナーを2回以上実行したときの挙動が変わるためである。必要以上に . が配列の要素に代入されるわけだからもっともだ。

上記の解答例だと $g が空文字列の場合にエラーを吐いてしまう(実際に試して確認した)。そこで

do [[ "$g" = "" ]] || x[$g]+=.

と書き換える。 "$g"= "" のところで $g が空文字だった場合、OR演算子 || でつながっていることから x[$g]+=. の処理が行われないようになっているのだとわかった。

 expression1 || expression2
                     expression1 と expression2 のどちらかが真であれば真となります。

              expression1 の値だけで条件式全体の返り値が決定できれば、 && 演算子と || 演算子は expression2 を実行しません。

という仕様なので処理が行われないわけか。