【文字列処理を awk で】テキストの不要な行・空行を除いて必要な行だけをサクッと抽出するコマンド処理＋α（特定の列への処理も）

とある作業を行っている中で、この後に例示するような構成のテキストから、特定の部分のみを抽出したいということがありました。

その時、「サクッとテキスト処理を行う方法って何があったっけ？」と思って調べて出てきたのが awk でした。awk　は今回が初利用ではなく、過去にサーバーログのテキストをあれこれ処理する作業があった際に、利用したことがあったものだったので、今回利用することにしました。

そして awk を使うのが久しぶりすぎるのと、以前使った回数が数えるほどだけで短い期間でのことだったため、そもそもの使い方を調べなおすところからやることに。。。
それで、将来の自分へのメモをかねた記事を残しておくと良さそうと思ったため、この記事を書きました。

## 今回の処理対象
今回の処理対象の話と実行するコマンドの話に入ります。
（なおテキストの構成の記載は、実際に処理したデータの内容を少しぼかして書いたものになります）

### 1）抽出したい情報が、一定の行数毎に出てくる（3行がセットになったような構成）
まず 1つ目は、以下のような構成の話です。
タイムスタンプなどのメタ情報が書かれた行と、コンテンツ本体にあたる部分とが 1行ずつのセットで書かれており、その組み合わせがその後にも空行をはさんで書かれているような形です。

```
（タイムスタンプ等1）
（抽出したい部分1）

（タイムスタンプ等2）
（抽出したい部分2）

（タイムスタンプ等3）
（抽出したい部分3）

。。。
```

上記の例では、2行目・5行目・8行目。。。を抜き出す処理になります。
「先頭に空行を追加してしまって、3行目から 3行ごとに抽出する」という感じにするとシンプルな処理になりそうです。

上記の内容（で、先頭に空行を追加したもの）が「awkSample1.txt」というファイル名のテキストだとして、それに対して抽出処理を行った結果を画面上に表示するコマンドを実行します。

```zsh
% cat awkSample1.txt| awk 'NR%3==0{print $0}'
```

catコマンドでテキストファイルの中身を取得して、それを awkコマンドの処理に渡します。
awk の処理の「NR」は現在の行を示すもので、 `NR%3==0` としているので 3行目・6行目・9行目。。。という行を対象に `print $0` という処理が実行される形です。この「print $0」は「その行を表示する」という処理になるため、3行目から 3行ごとの内容を表示する、という処理になります。

なおファイルに対する処理は、「`awk 'コマンド' 【ファイル名】」という書き方もできます。それを用いる場合のコマンドは、以下のとおりです。

```zsh
% awk 'NR%3==0{print $0}' awkSample1.txt
```

#### 参考
●とほほのAWK入門 - とほほのWWW入門
　https://www.tohoho-web.com/ex/awk.html
●Linuxでawk/sedを使ってファイルから奇数行・偶数行のみを抽出する | 俺的備忘録 〜なんかいろいろ〜
　https://orebibou.com/ja/home/201607/20160712_002/
●【 awk 】コマンド（応用編その6）――テキストの加工とパターン処理、複数ファイルの処理：Linux基本コマンドTips（212） - ＠IT
　https://atmarkit.itmedia.co.jp/ait/articles/1806/01/news041.html

### 2）抽出したい情報が、一定の行数毎に出てくる（4行がセットになったような構成で空行が不規則）
次は、以下のようなメタ情報の行が 2行と抽出したい部分の行が 1行あり、その後に空行をはさんで同じようなセットがある、という構成です。ただし、空行の数が不規則なものになっています。

```
（メタ情報1行目1）
（メタ情報2行目1）
（抽出したい部分1）


（メタ情報1行目2）
（メタ情報2行目2）
（抽出したい部分2）

（メタ情報1行目3）
（メタ情報2行目3）
（抽出したい部分3）



（メタ情報1行目4）
（メタ情報2行目4）
（抽出したい部分4）

。。。
```

こちらは空行が不規則に入っていますが、空行以外の部分は規則的なので、空行を除いた後に先ほど行ったような処理をするのが良さそうです。「awkSample2.txt」というファイル名のテキストにこの内容が書かれているとして、それに対して処理をするコマンドを実行します。

```zsh
% cat awkSample2.txt| awk '$0 != ""{print $0}' | awk 'NR%3==0{print $0}'
```

いったん、空行を除く処理を行い、その結果に対して 3行ごとの内容を抽出する処理を行いました。空行の除去は `$0 != ""` という条件で、行の中身が空でない場合に出力を行うものになっています。

#### 参考（追加分）
●awkで空白行を削除する | ITを使っていこう
　https://it-ojisan.tokyo/awk-blank-line-delete/

## ＋αの部分
今回 awk についてあらためて調べていた中で出てきて、前に使った気がするものでメモしておこうとおもったものを 1つ書いておこうと思います。

### 特定の列に対する処理
何らかの区切り文字で区切られたデータを対象にした処理の話になります。

#### 単純に取り出す

```
（メタ情報1列目1） （メタ情報2列目1） （抽出したい部分1）
（メタ情報1列目2） （メタ情報2列目2） （抽出したい部分2）
（メタ情報1列目3） （メタ情報2列目3） （抽出したい部分3）
。。。
```

スペース区切りで、メタ情報 2つ と抽出した内容が 1つ、合計 3列になる形のデータです。「awkSample3.txt」というファイル名のテキストにこの内容が書かれているとして、それに対して処理をするコマンドを実行します。

```zsh
% cat awkSample3.txt| awk '{print $3}'
```

上の例では `$0` という行全体を表すものを使っていましたが、ここで出てくる `$3` は今回のスペース区切りの内容の 3列目を示すものです。ちなみに `$1` と指定してやれば「メタ情報1列目」の部分を抽出することができます。
なお、データの列ごとの区切りがカンマ区切りだった場合、このコマンドに少し手を加える必要があります。区切り文字を指定するオプションが `-F` で、これを使ってカンマ区切りの 3列目を指定する場合は `awk -F ',' '{print $3}'` というような書き方になります。

#### substr() による文字の切り出し
特定の列を取り出す処理に加えて、文字の切り出しも行ってみます。以下のページなどにも書かれている substr() を使います。

●awkでsubstr()を使って文字列を切り出す方法 | ITを使っていこう
　https://it-ojisan.tokyo/awk-substr/

例えば 1列目の内容を取り出して、その中の「2文字目から 1文字分を切り出す」という場合は、以下のような書き方になります（ `substr($1,2,1)` という指定）。

```zsh
% cat awkSample3.txt| awk '{print substr($1,2,1)}'
```

もし「2文字目から 2文字分を切り出す」という場合は、以下のようになります。

```zsh
% cat awkSample3.txt| awk '{print substr($1,2,2)}'
```

上記の `substr()` で日本語を対象にしたら表示が文字化けしたのですが、それについて調べたら、以下のような注意点があるようです。自分は Mac の awk という部分で「正しく扱えない環境」に該当しました。

●【注意】POSIX awk は日本語文字 (UTF-8)を正しく扱うことが出来ません - Qiita
　ttps://qiita.com/ko1nksm/items/c9f8e2afa1eb12d3a3b7

あと、その文字化け表示で出てくる文字に関するメモを。

●黒いひし形にはてなマークが出る文字（�）はUTF-8変換時のときに変換後の対象がない置き換え文字 - コード日進月歩
　https://shinkufencer.hateblo.jp/entry/2019/04/05/233000