特定の行と、その上２行を消したい。





##目的
データ内の特定行と、その上２行を消したい。

具体的に考えてみよう。

 下記のようなデータファイル before.dat を考える。
そこには、森（探索先）で出会った生き物（対象）の有無が記録されている。出会えなかった生き物の下には ”nodata” と書いている。

```before.dat
探索先　森
対象 カブトムシ
探索先　森
対象 金魚
nodata
探索先 森
対象 きつね
探索先　森
対象　ネッシー
nodata
```
このデータから、出会えた生き物だけのデータが記載されたデータファイル after.data を作りたい。

``` after.data
探索先　森
対象 カブトムシ
探索先 森
対象 きつね
```

さて、どうしよう？


##方法

“nodata” と書かれた行と、その上２行を消せば良いだろう。

使うのはBashシェルスクリプト。
ワイが考えた手順を示そう。以下の４ステップだ。

1. 各行を配列に収納
1. その配列の中から、要素 "nodata" の添字を見つける。
1. 要素 "nodata" の添字を番号 i とすると、添字 i, i-1, i-2 の要素を削除。
1.  要素を削り終わった後の配列を行に戻す。 

以上。
まずは、結果のシェルスクリプトextract.sh を下に示す。
その次にスクリプトの書き方を解説する。結果を読んでチンプンカンプンだった人は解説を読んでね。

##結果

```bash:extract.sh
#!/bin/bash
### ステップ
#1 各行を配列に収納。
#2 その配列の中から、要素 "nodata" の添字を見つける。
#3 要素 "nodata" の添字を番号 j とすると、添字 j, j-1, j-2 の要素を配列から削除。
#4　要素を削り終わった後の配列を、行に戻す。

# 型宣言。変数ｊを数値（integer）用に定義する。ついでに１を代入。
declare -i j=1
# while read line の返戻値がfalseのときでも、空行じゃない場合は処理するようなOR文を入れる。
while read line || [ -n "${line}" ];
do
    #ステップ１ 各行を配列に収納。
    array[$j]="$line"
    #ステップ２ 配列の中から、要素 "nodata" の添字を見つける。
    if [ "${array[$j]}" = "nodata" ]; then
        #ステップ３ 要素 "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)

#ステップ４　要素を削り終わった後の配列を、行に戻す。
IFS=$'\n'
echo "${array[*]}" >> after.dat

#@tama2ka
```

##詳しい解説

cat コマンドでファイルの中身を標準出力として出力する。それをパイプ｜で while に渡せば行けそう。

```bash:extract.sh
#!/bin/bash

# 型宣言。変数ｊを数値（integer）用に定義する。ついでに１を代入。
declare -i j=1
cat before.dat | while read line
do
    #ステップ１ 各行を配列に収納。
    array[$j]="$line"
    echo "${array[$j]}"
done
```
出力結果

```
探索先 森
対象 カブトムシ
探索先 森
対象 金魚
nodata
探索先 森
対象 きつね
探索先 森
対象 ネッシー
```
あれ？最後の行なくね？
ということで、下記のように修正。[（参考）](https://qiita.com/Ets/items/a7fa24b138b8ee883dac#:~:text=%E3%81%A6%E3%81%84%E3%82%8B%E3%81%8B%E3%82%89%E3%81%A0%E3%80%82-,%E8%A7%A3%E6%B1%BA,-while%20read%20LINE)

```bash:extract.sh
#!/bin/bash

# 型宣言。変数ｊを数値（integer）用に定義する。ついでに１を代入。
declare -i j=1
cat before.dat | while read line || [ -n "${line}" ];
do
    #ステップ１ 各行を配列に収納。
    array[$j]="$line"
    echo "${array[$j]}"
done
```
上手くいったので、配列の中のnodataを探す。そいつと、そいつの前２つを消す。

```bash:extract.sh
#!/bin/bash

# 型宣言。変数ｊを数値（integer）用に定義する。ついでに１を代入。
declare -i j=1
cat before.dat | while read line
do
    #ステップ１ 各行を配列に収納。
    array[$j]="$line"
    #ステップ２ 配列の中から、要素 "nodata" の添字を見つける。
    if [ "${array[$j]}" = "nodata" ]; then
         #ステップ３ 要素 "nodata" の添字を番号 j とすると、添字 j, j-1, j-2 の要素を配列から削除。
         unset array[$j]
         unset array[$j-1]
         unset array[$j-2]
     fi
     
     j+=1

done
```

不要な要素を配列から削除したので、行に戻そう！

```bash:extract.sh
#!/bin/bash

# 型宣言。変数ｊを数値（integer）用に定義する。ついでに１を代入。
declare -i j=1
cat before.dat | while read line
do
    #ステップ１ 各行を配列に収納。
    array[$j]="$line"
    #ステップ２ 配列の中から、要素 "nodata" の添字を見つける。
    if [ "${array[$j]}" = "nodata" ]; then
         #ステップ３ 要素 "nodata" の添字を番号 j とすると、添字 j, j-1, j-2 の要素を配列から削除。
         unset array[$j]
         unset array[$j-1]
         unset array[$j-2]
     fi
     
     j+=1

done

#ステップ４　要素を削り終わった後の配列を、行に戻す。
IFS=$'\n'
echo "${array[*]}" >> after.dat
```
上手くいったと思ったら、最後の行の \${array[*]} に何も入っていないじゃないか！！
while の中の array[$j] の情報が、while の外に引き継がれていない。
そこで、引き継がれるように、while への情報の渡し方を変更。cat は使わず、下記のように done の後ろに持っていく。[（参考）](https://qiita.com/kawaz/items/6fd4cd86ca98af644a05#:~:text=%E3%81%93%E3%81%A8%E3%81%AB%E3%81%AA%E3%82%8A%E3%81%BE%E3%81%99%E3%80%82-,%E8%A7%A3%E6%B1%BA%E7%B7%A8,-%E3%81%93%E3%82%8C%E5%AE%9F%E3%81%AF%E4%B8%8A%E6%89%8B%E3%81%84)


```bash:extract.sh
#!/bin/bash

# 型宣言。変数ｊを数値（integer）用に定義する。ついでに１を代入。
declare -i j=1
while read line || [ -n "${line}" ];
do
    #ステップ１ 各行を配列に収納。
    array[$j]="$line"
    #ステップ２ 配列の中から、要素 "nodata" の添字を見つける。
    if [ "${array[$j]}" = "nodata" ]; then
        #ステップ３ 要素 "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)

#ステップ４　要素を削り終わった後の配列を、行に戻す。
IFS=$'\n'
echo "${array[*]}" >> after.dat
```

完成！！