目的
データ内の特定行と、その上2行を消したい。
具体的に考えてみよう。
下記のようなデータファイル before.dat を考える。
そこには、森(探索先)で出会った生き物(対象)の有無が記録されている。出会えなかった生き物の下には ”nodata” と書いている。
探索先 森
対象 カブトムシ
探索先 森
対象 金魚
nodata
探索先 森
対象 きつね
探索先 森
対象 ネッシー
nodata
このデータから、出会えた生き物だけのデータが記載されたデータファイル after.data を作りたい。
探索先 森
対象 カブトムシ
探索先 森
対象 きつね
さて、どうしよう?
方法
“nodata” と書かれた行と、その上2行を消せば良いだろう。
使うのはBashシェルスクリプト。
ワイが考えた手順を示そう。以下の4ステップだ。
- 各行を配列に収納
- その配列の中から、要素 "nodata" の添字を見つける。
- 要素 "nodata" の添字を番号 i とすると、添字 i, i-1, i-2 の要素を削除。
- 要素を削り終わった後の配列を行に戻す。
以上。
まずは、結果のシェルスクリプトextract.sh を下に示す。
その次にスクリプトの書き方を解説する。結果を読んでチンプンカンプンだった人は解説を読んでね。
結果
#!/bin/bash
### ステップ
#1 各行を配列に収納。
#2 その配列の中から、要素 "nodata" の添字を見つける。
#3 要素 "nodata" の添字を番号 j とすると、添字 j, j-1, j-2 の要素を配列から削除。
#4 要素を削り終わった後の配列を、行に戻す。
# 型宣言。変数jを数値(integer)用に定義する。ついでに1を代入。
declare -i j=1
# while read line の返戻値がfalseのときでも、空行じゃない場合は処理するようなOR文を入れる。
while read line || [ -n "${line}" ];
do
#ステップ1 各行を配列に収納。
array[$j]="$line"
#ステップ2 配列の中から、要素 "nodata" の添字を見つける。
if [ "${array[$j]}" = "nodata" ]; then
#ステップ3 要素 "nodata" の添字を番号 j とすると、添字 j, j-1, j-2 の要素を配列から削除。
unset array[$j]
unset array[$j-1]
unset array[$j-2]
fi
j+=1
done < <(cat before.dat)
#ステップ4 要素を削り終わった後の配列を、行に戻す。
IFS=$'\n'
echo "${array[*]}" >> after.dat
#@tama2ka
詳しい解説
cat コマンドでファイルの中身を標準出力として出力する。それをパイプ|で while に渡せば行けそう。
#!/bin/bash
# 型宣言。変数jを数値(integer)用に定義する。ついでに1を代入。
declare -i j=1
cat before.dat | while read line
do
#ステップ1 各行を配列に収納。
array[$j]="$line"
echo "${array[$j]}"
done
出力結果
探索先 森
対象 カブトムシ
探索先 森
対象 金魚
nodata
探索先 森
対象 きつね
探索先 森
対象 ネッシー
あれ?最後の行なくね?
ということで、下記のように修正。(参考)
#!/bin/bash
# 型宣言。変数jを数値(integer)用に定義する。ついでに1を代入。
declare -i j=1
cat before.dat | while read line || [ -n "${line}" ];
do
#ステップ1 各行を配列に収納。
array[$j]="$line"
echo "${array[$j]}"
done
上手くいったので、配列の中のnodataを探す。そいつと、そいつの前2つを消す。
#!/bin/bash
# 型宣言。変数jを数値(integer)用に定義する。ついでに1を代入。
declare -i j=1
cat before.dat | while read line
do
#ステップ1 各行を配列に収納。
array[$j]="$line"
#ステップ2 配列の中から、要素 "nodata" の添字を見つける。
if [ "${array[$j]}" = "nodata" ]; then
#ステップ3 要素 "nodata" の添字を番号 j とすると、添字 j, j-1, j-2 の要素を配列から削除。
unset array[$j]
unset array[$j-1]
unset array[$j-2]
fi
j+=1
done
不要な要素を配列から削除したので、行に戻そう!
#!/bin/bash
# 型宣言。変数jを数値(integer)用に定義する。ついでに1を代入。
declare -i j=1
cat before.dat | while read line
do
#ステップ1 各行を配列に収納。
array[$j]="$line"
#ステップ2 配列の中から、要素 "nodata" の添字を見つける。
if [ "${array[$j]}" = "nodata" ]; then
#ステップ3 要素 "nodata" の添字を番号 j とすると、添字 j, j-1, j-2 の要素を配列から削除。
unset array[$j]
unset array[$j-1]
unset array[$j-2]
fi
j+=1
done
#ステップ4 要素を削り終わった後の配列を、行に戻す。
IFS=$'\n'
echo "${array[*]}" >> after.dat
上手くいったと思ったら、最後の行の \${array[*]} に何も入っていないじゃないか!!
while の中の array[$j] の情報が、while の外に引き継がれていない。
そこで、引き継がれるように、while への情報の渡し方を変更。cat は使わず、下記のように done の後ろに持っていく。(参考)
#!/bin/bash
# 型宣言。変数jを数値(integer)用に定義する。ついでに1を代入。
declare -i j=1
while read line || [ -n "${line}" ];
do
#ステップ1 各行を配列に収納。
array[$j]="$line"
#ステップ2 配列の中から、要素 "nodata" の添字を見つける。
if [ "${array[$j]}" = "nodata" ]; then
#ステップ3 要素 "nodata" の添字を番号 j とすると、添字 j, j-1, j-2 の要素を配列から削除。
unset array[$j]
unset array[$j-1]
unset array[$j-2]
fi
j+=1
done < <(cat before.dat)
#ステップ4 要素を削り終わった後の配列を、行に戻す。
IFS=$'\n'
echo "${array[*]}" >> after.dat
完成!!