理科系の備忘録

Linux/Ubuntu/Mac/Emacs/Computer vision/Robotics

シェルスクリプトで可変長データを読み取る

はじめに

こんなデータに出くわした。data.txtとする。

1, hoge, foo, bar, piyo, [ID: 1; a; b; c; d;ID: 2; a; b; c; d;ID: 3; a; b; c; d;ID: 4; a; b; c; d;ID: 5; a; b; c; d;]
2, hoge, foo, bar, piyo, [ID: 1; a; b; c; d;]
3, hoge, foo, bar, piyo, [ID: 1; a; b; c; d;ID: 2; a; b; c; d;ID: 3; a; b; c; d;]
4, hoge, foo, bar, piyo, [ID: 1; a; b; c; d;ID: 2; a; b; c; d;]

[]で囲まれた部分が可変長のデータになっている。可変長部分から'b'だけを取り出して、縦1列に並べたい。

1   b
2   b
3   b
4   b
5   b
1   b
1   b
2   b
3   b
1   b
2   b

今回一回限りのデータ処理だったので、久々にシェルで遊ぶことにした。以下は考えた順番通りのメモ。もっといい方法はあると思うが。

実践

下ごしらえとしてawkで[ ]の部分だけを取り出す。[ ]の中と外でセパレータが違うのでありがたい。
簡単のため、最初の1行だけで処理を考える。

head -1 data.txt | awk -F ',' '{print $6}'
[ID: 1; a; b; c; d;ID: 2; a; b; c; d;ID: 3; a; b; c; d;ID: 4; a; b; c; d;ID: 5; a; b; c; d;]

awkでforを回すことも考えた。しかし今回は先に試した別の方法がうまく機能した。

head -1 awk -F, '{print $6}' | tr "ID:" "\nID:" | awk -F ';' 'NR>1{print $3}'

'ID'を検索して、'ID'が見つかるたびに改行する。すると

 [
ID 1; a; b; c; d;
ID 2; a; b; c; d;
ID 3; a; b; c; d;
ID 4; a; b; c; d;
ID 5; a; b; c; d;]

と複数の行であらわされた!あとは各行について、awkで'b'を取り出すだけだ。1行目は'['が邪魔なのでNR>1で回避しておく。

最後に、元データの各行についてこれを実行すればいいから、head -1 の部分をwhile readに変更して1行ずつ読み取るようにした。

cat data.txt | while read LINE;
do
    echo $LINE | awk -F, '{print $6}' | tr "ID:" "\nID:" | awk -F ';' 'NR>1{print $3}' | cat -n;
done
1   b
2   b
3   b
4   b
5   b
1   b
1   b
2   b
3   b
1   b
2   b

ついでに

一応関数化してみる。'cat -' で標準入力を受けられるものにした。

read_multi_frame(){
    cat - |
	awk -F, '{print $6}' |
	tr "ID:" "\nID:" |
	awk -F ';' 'NR>1{print $3}'
}
cat data.txt | while read LINE;
do
    echo $LINE | read_multi_frame | cat -n;
done

以上。