【 awk 】コマンド(応用編その5)――テキストの加工とパターン処理、複数行のレコード処理Linux基本コマンドTips(211)

本連載は、Linuxのコマンドについて、基本書式からオプション、具体的な実行例までを紹介していきます。今回は、テキストのパターン処理を行う「awk」コマンドです。

» 2018年05月31日 05時00分 公開
[西村めぐみ@IT]
「Linux基本コマンドTips」のインデックス

Linux基本コマンドTips一覧

 本連載は、Linuxのコマンドについて、基本書式からオプション、具体的な実行例までを紹介していきます。今回はテキストのパターン処理を行う「awk(gawk)」コマンドです。

 連載第115回第116回第117回第118回第119回第120回第209回第210回に続き、awkの応用を説明します。

awk(オーク)コマンドとは?

 「awk」は空白などで区切られたテキストを処理するコマンドです。演算機能もあり、プログラミング言語としても使用されています。

 Linux環境で使用されているのは、GNUプロジェクトによる「gawk」コマンドが多く、例えばCentOS 7の場合、awkは/usr/bin/gawkへのシンボリックリンクとなっています。

 Ubuntu 15では、Michael D. Brennan氏による「mawk」が収録されています(awkは/etc/alternatives/awkへの、/etc/alternatives/awkは/usr/bin/mawkへのシンボリックリンク)。

 どちらも、もともとのawkに加えてPOSIX 1003.2への準拠や組み込み変数、正規表現指定のバリエーションなどが拡張されています。



awkコマンドの書式

awk [オプション] [コマンド] [ファイル……]

※[ ]は省略可能な引数を示しています





awkの主なオプション

短いオプション 意味
-f ファイル名 awkスクリプトが書かれたファイルを指定する
-F 区切り文字 区切り文字を指定する(デフォルトは空白文字)
-v 変数名=値 変数を定義する

※ gawk(GNU版awk)の場合、長いオプションも使用可能。-fは--file program-file、-Fは--field-separator、-vは--assign。gawkにはこの他にも多数のオプションがある。



awkで使用できる主な組み込み変数

変数名 意味
ARGC 引数の個数
ARGV 引数(配列)
ENVIRON 環境変数を収めた連想配列。例えば環境変数LANGならばENVIRON["LANG"]と参照できる
FILENAME 現在処理しているファイルの名前
FNR 現在処理しているファイルのレコード番号(処理しているファイルが1つの場合はNRと同じ値になる)
FS フィールドの区切り文字(-Fオプションで変更可能、デフォルトはスペース)
NF 現在処理しているレコードのフィールド数
NR 現在処理しているレコード番号(行番号)
OFS 出力時のフィールドの区切り(デフォルトは空白)
ORS 出力時のレコードの区切り(デフォルトは改行)
RS レコードの区切り(デフォルトは改行)

awkで使用できる主な文字列操作用の関数

関数(引数) 意味
gsub(r, s, t) 「文字列t」中で「正規表現r」にマッチした箇所全てをsに置き換えて、置き換えた個数を返す。tを指定しなかった場合は「$0」(読み込んだ行全体)が対象
index(s, t) 「文字列s」に含まれる文字列tの位置を返す。tが含まれていない場合は0を返す
length(s) 「文字列s」の長さを返す。sを指定しなかった場合には「$0」の長さを返す
match(s, r) 「文字列s」で「正規表現r」にマッチする位置を返す。その際、内部変数のRSTARTに開始位置、RLENGTHに長さをセットする。マッチしない場合は0を返す
split(s, a, r) 「文字列s」を「正規表現r」で分割し、配列aに格納する。rを省略した時は「FS」(区切り文字、デフォルトは空白)となる
sprintf(フォーマット指定, 変数リスト) フォーマット指定に従って整形した文字列を返す。指定方法は「printf」(第118回)と共通
sub(r, s, t) 「文字列t」の中で「正規表現r」へ最初にマッチした箇所をsに置き換える。tを指定しなかった場合は「$0」が対象
substr(s, i, n) 「文字列s」のi文字目からn文字分を返す。nを省略した場合はi文字目以降全てを返す
tolower(s) 「文字列s」のうち大文字を全て小文字に変換したものを返す
toupper(s) 「文字列s」のうち小文字を全て大文字に変換したものを返す

※ この他、sin()やlog()、rand()などの数値関数、さらにgawkの場合はsystime()やmktime()などの時間関数を利用できる。




空行区切りのデータを処理する

 これまで連載で扱ってきたawk用のデータは、テキストの1行がそのままレコードになっていました。awkは、以下のaddress.txtのようにレコードが複数行にまたがったデータも処理できます。address.txtでは、各レコードが空行で区切られています。

東川 雄一
080-1111-1111
〒111-1111
××県××区A町 1-1-1
ABCビル1001
	
西村 祐二
080-2222-2222
〒222-2222
××県××市B町 2-22
	
南山 裕三
080-3333-3333
〒333-3333
××県××市C町 3-3-3
XYZハイツ3号室
	
北岡 優四
080-4444-4444
〒444-4444
××県××区D町 4-4-4
address.txt(表示の都合上ブロックを区切る改行の代わりにタブを入れた)

 このような空行区切りのデータを処理するには、レコードの区切り文字(RS)を「""」に設定します。address.txtでは1つのレコードの中で、氏名や電話番号などが改行で区切られているので、フィールドの区切り文字(FS)は「\n」とします。

 画面1では、catコマンド(第1回)でaddress.txtを出力し、awkコマンドで、各ブロックの1行目(氏名の行)と2行目(電話番号)だけを出力しています。

コマンド実行例

cat address.txt | awk 'BEGIN{RS="";FS="\n"}{print $1 $2}'

(レコード区切りを空行、フィールド区切りを改行に設定してデータを処理する)


画面1 画面1 1レコードが複数行にまたがるデータファイルから、1行目と2行目を全て取り出してレコードごとに1行で表示したところ


対象にしたいデータを指定する

 1レコードが複数行にまたがっている場合でも、処理の対象にしたいデータを指定する方法は変わりません。通常のパターン指定と同じ方法で進めます(第116回)。

 先ほどのaddress.txtで、「××区」が含まれているデータだけを対象としたい場合は、「/××区/{print $1 $2}」のように指定します。

 画面2では、次のawkスクリプトをコマンドラインで直接記述しました。

BEGIN{
  RS=""
  FS="\n"
}
/××区/{
  print $1 $2
}

コマンド実行例

cat address.txt | awk 'BEGIN{RS="";FS="\n"}/××区/{print $1 $2}'

(「××区」が含まれるレコードを抜き出す)


画面2 画面2 画面1と同じデータファイルを扱い、「××区」が含まれるレコードだけを抜き出したところ


空行区切りのデータを集計する

 レコードが複数行にまたがったデータを扱う際でも、データの集計をawkコマンドを使って処理できます。

 以下のorder.txtは、空行区切りのデータです。各ブロックの1行目が人名、残りが注文と備考データです。注文と備考の行は全てタブで区切られています。注文の行の内容は、品名(A〜D)、単価、個数となっています。

東川 雄一
注文	A	100	10
注文	B	200	5
	
西村 祐二
注文	B	200	10
	
南山 裕三
注文	C	300	15
注文	D	400	20
	
北岡 優四
注文	B	195	5
注文	C	295	15
注文	D	395	20
備考	優待チケットあり
order.txt(表示の都合上ブロックを区切る改行の代わりにタブを入れた)

 この場合も、先ほど同様、レコードの区切りを空行(RS="")、各レコードのフィールドの区切りを改行(FS="\n")と指定します。注文と備考の行はawkのsplit関数(第210回)を使って分割します。

 以下のtotal.awkでは、それぞれのレコードについて、total配列に顧客ごとの金額を集計し、count配列に品物ごとの個数を集計しました。ENDブロックではprintで金額や個数を出力しています。出力を桁ぞろえしたい場合はprintfを使用するとよいでしょう(第118回)。

 画面3では処理が長くなったのでスクリプトファイル(total.awk)を使って「awk -f スクリプトファイル データファイル」を実行しています。

BEGIN{
  RS=""
  FS="\n"
}
{
  for (i=1;i<=NF;i++){
    split($i,d,"\t")		#各行(フィールド)をタブ区切りで分割して配列dにセット
    if (d[1] == "注文"){	#「注文」の場合
      total[$1] += d[3]*d[4]	#人(各ブロックの1行目)毎の値段を加算
      count[d[2]] += d[4]	#品物(注文行の2列目)毎の個数を加算
    }
  }
}
END{
  for (i in total){
    print i, total[i]
  }
  for (i in count){
    print i, count[i]
  }
}
total.awk

コマンド実行例

awk -f スクリプトファイル データファイル

(awkスクリプトファイルを読み込み、データファイルを処理する)

awk -f total.awk order.txt

(total.awkを読み込み、order.txtを処理する)


画面3 画面3 注文ファイルを読み込み、顧客ごとと品名ごとに集計したところ


筆者紹介

西村 めぐみ(にしむら めぐみ)

PC-9801NからのDOSユーザー。PC-486DX時代にDOS版UNIX-like toolsを経てLinuxへ。1992年より生産管理のパッケージソフトウェアの開発およびサポート業務を担当。著書に『図解でわかるLinux』『らぶらぶLinuxシリーズ』『Accessではじめるデータベース超入門[改訂2版]』『macOSコマンド入門』など。2011年より、地方自治体の在宅就業支援事業にてPC基礎およびMicrosoft Office関連の教材作成およびeラーニング指導を担当。


Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。