LoginSignup
0
0

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-02-20

目的

データ内の特定行と、その上2行を消したい。

具体的に考えてみよう。

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

before.dat
探索先 森
対象 カブトムシ
探索先 森
対象 金魚
nodata
探索先 森
対象 きつね
探索先 森
対象 ネッシー
nodata

このデータから、出会えた生き物だけのデータが記載されたデータファイル after.data を作りたい。

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

さて、どうしよう?

方法

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

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

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

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

結果

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 に渡せば行けそう。

extract.sh
#!/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
探索先 森
対象 きつね
探索先 森
対象 ネッシー

あれ?最後の行なくね?
ということで、下記のように修正。(参考)

extract.sh
#!/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つを消す。

extract.sh
#!/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

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

extract.sh
#!/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 の後ろに持っていく。(参考)

extract.sh
#!/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

完成!!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0