【0060】文字列→配列の変換でxargsを使ってハマった
そもそもシェルスクリプトを
シェルスクリプトらしく効率的に使おうとする場合
配列は余り意識しないべきなんだという気はしています。
配列自体は作ってもソレ要素分反復するとかいう記述は
シェルスクリプト的じゃないというか
イテレーティブに扱えるデータならそのまま次の処理に放り投げて
要素数とか番号とかを念頭には置くべきじゃないんではないか
という感じがしてきています。
が。
まあ…パイプでつらつらつながってたり、
ワンライナーで複数の仕事をしているような記述って
…可読性悪いですよね。
それならbashでやるのやめろって話ですけど。
話を戻しますと、
bashには連想配列はあっても普通の二次元配列は無いそうです。
既に一部で困ったため、
IFS文字を「:」に変えるなどして
文字列として受け取ったものを分解する処理は取り込んでいたんですが
いよいよ大きく困ったことが現れました。
アイテムです。
前回アイテム表示領域を雑に作りましたけど、
アイテムデータをどうやって持つかについて相当困りました。
ローグ系のシステムでアイテムの扱いは
・持ち物として表現されるアイテム
・落ちているアイテム
の2つです。
前者の「持っているアイテム」もただシステム的に配列か何かに持っていればいいというわけでもないのは、表示上に現れないステータスを所有していることです。例えば「薬草」であれば回復量など。
「説明文」ではなく、実際に回復する量の「データ」は持ち物には登場しません。
こうしたデータを扱うには、
アイテムをデータ構造として保持する必要が有るのですが
アイテムID:0001
アイテム名:薬草
マップ上の表示:O
効果:HP回復(30)
売値:銅貨5枚
腐るかどうか:腐る
とか。
で、プレイヤーの持ち物としてはアイテムIDの配列にして
なにかデータが必要になる度にデータ構造に問い合わせをして取得するのが妥当なわけです。
今マップチップの判断でやっていることの巨大なやつが、
アイテム周りでは必要だということに気づきます。
やべえ、超めんどくせえ。
これが、「持っているアイテム」問題。
「落ちているアイテム」問題もあります。
まず落ちているアイテムは、
・最初に上に乗ったタイミングでアイテム抽選が実施される
・抽選後は確定したアイテム名で判断される
・拾うとそのとおりのアイテムが持ち物に追加される
・置くコマンドがアレば、おいた場合には確定アイテムが画面に設置される
・当然おいたアイテムの上に乗った場合は抽選はなく確定アイテムとなる
やべえ、クソクソめんどくせえ。
どうやって実装するのがいいのか、微塵も思いつきません。
いずれにしても、
「アイテムの抽選」という機能が必要で、
抽選するにもやはり
「どのアイテムIDのアイテムになったか」を乱数で取得して
その他の情報はアイテムのデータ構造体に問い合わせて
取得することになります。
この構造体をどうやって持つのがいいのかはわかりません
理想で言うとsqliteかなんかでも入れて
DB化するのがいいのかもしれませんが、
そんな高尚なシステムにするのも嫌なので、
スクリプト内にcsv的に腹持ちしようと思います。
…愚策か?
まずアイテムテーブルがあり、アイテムのマスタテーブルとなります。
実際には、アイテムの情報を1行内に持ったデータ…これが配列持ちされるのかなと思います。
次が持ち物テーブルで、現在所持しているアイテムのアイテムIDを配列で持つことに。
更にアイテムの抽選にも配列を使うハメになるだろうことが予測されます。
(どんなアイテムでもランダムに手に入るわけではなく、武器表示の上に乗れば武器の中から抽選、しかもその階層かレベルにある程度従った中からですから、そんな思い通りの乱数を発生させる摩訶不思議なマトリクス計算式を用意するくらいならある程度抽選テーブルを分割しておいてその中から有る法則に従ったランダム取得、というのが妥当に思えます。)
突然大量に関数結果に配列返しが必要になるのですが、
その方法についてコーディング上の規約を設けていませんでした。
今まであったのは上記の通り「:」で区切るケースが有ると言うだけ。
いずれにせよ、bashでは関数の返却に配列は使えません。
いや、使えるんですけど、単純に配列を標準出力として呼び出し元に返す場合にはタダのひと続きの文字列になるということです。
マップチップの判定や方向指示後の対象マスの座標算出で行っているように
関数の戻りとして、文字列の顔をした配列を戻されるケースが今後頻出するとなると、その記載については統一を期したい。
配列への代入は、こうです。
配列名=(IFS文字で区切られた要素たち)
arry=(1 2 3 4 5)
なので、bashでは配列を関数呼び出し元に返すことは出来ないので
配列を返したい関数は、配列ではなくこれらの文字列
'1 2 3 4 5'
を返すのが常套だそうです。
最初の話に戻しますと、
二次元配列はないものの
要素に「二次元目の配列の顔をしたタダの文字列」をもつ一次元配列
は出来るわけですね。
なので、これを二次元配列の代わりに使うというのも常套のようです。
マップチップ判定ではスペース区切り
方向指示対象マス座標取得でも当初「:」区切り→今はスペース区切り
となっていますが、その方法がまちまちで統一的ではありませんでした。
どうやるのがきれいなのか。
同じようなことを考えている人はいっぱいいるようですが、その中で一番スマートそうだったのが、
xargsコマンドを使用した分解です。
これは、文字列として渡された
’-a -l’
とかいう文字列を、
別コマンドへの引数に分解するためのコマンドのようです。
ls ’-a -l’
では困るので、きちんと
ls -a -l
にするのが目的のようです。
引数は、タダのひとかたまりの文字列ではなく第一引、数第二引数…と指定できるようによしなに分割されるのでしょう。
「この文字列はスペースで区切られている引数の集合である、バラせ」
という命令をするのがxargsであって、
別に文字列を配列に直すための機能ではないのだと思います。
が、これがどうにも使えるのだということですね。
str='1 2 3 4 5'
arry=($(echo $str|xargs))
とやると、配列arryには1,2,3,4,5の要素が突っ込まれるんだそうです。
意味わかりませんね。
arry=( $(echo $str|xargs) )
^^^^^^配列だよ ↓ ^^配列だよ
echo $strで'1 2 3 4 5'が標準出力される
↓
それがパイプ(|で示される)でxargsコマンドに渡されると
1 2 3 4 5 というバラバラの要素に分解される。
↓
配列の要素を示す()の中に放り込まれて、配列の要素そして認識される
ということらしいです。
シェル芸人が芸人と言われる意味がよく分かりますね。
パズルか。
また、このxargsコマンドは区切り文字をオプションで決めることが出来て、
xargs -d,
とか書くと、間膜切りで待ち受けてくれるそうです。
つまり、カンマ切りのデータを見事に線切りしてくれるのだそうですね。
何ソレすごい、csv使い放題じゃん?
って思うじゃん?
先にもかいた通り
xargsコマンドはあくまでも引数として使われる要素にバラす機能なので、
引数として無効なものは無視するようです。
csvで
アイテムID,アイテム名称,回復値,攻撃力
0001,どうのつるぎ,,10
0002,やくそう,30,,
というデータがあるとします。
どうのつるぎは武器なので回復値を持っていません。
やくそうは回復アイテムなので攻撃力を持っていません。
itemArrStr=’0001,どうのつるぎ,,10’
という文字列を分解しようとして
itemArry=(&(echo $itemArrStr|xargs -d,))
とやって、攻撃力データを取得しようとして
atk=${itemArry[3]}
とやると、値が取れません。
ぱっと見、
${itemArry[0]}に0001
${itemArry[1]}にどうのつるぎ
${itemArry[2]}に’’(空文字)
${itemArry[3]}に10
が入っていそうなものですが、
実際には
${itemArry[0]}に0001
${itemArry[1]}にどうのつるぎ
${itemArry[2]}に10
${itemArry[3]}に’’…実際には空ではなく未設定
となります。
おそらく、
xargsの本来の機能を鑑みると
コマンドに対する引数として「空白」という引数は渡す必要がないため
内容の無い要素についてはすっ飛ばすのでしょう。
まあまずはmanしろって話ですが。
ということで、配列分解に使おうとすると、
Null要素どころかスペースのみの要素も許されないため、
0などでパディングする必要があります。
スペースを含む文字列要素を分解させたい場合に限って
xargs -d,
は意味がありますが、
こうしたところでカンマ区切りの空要素を作れるわけではないようです。
これは大いにハマりました。
ということを踏まえて。
当スクリプトでは、
・配列にはNull要素を含ませないという規約を設けたうえで
・文字列の配列分解についてはxargsを使用する
・万一スペースを含む文字列を要素にしたい場合は-d,オプションをつける
ということで記述を統一しようと思います(統一しました)。
この記事が気に入ったらサポートをしてみませんか?