【 awk 】コマンド(応用編その6)――テキストの加工とパターン処理、複数ファイルの処理Linux基本コマンドTips(212)

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

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

Linux基本コマンドTips一覧

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

 連載第115回第116回第117回第118回第119回第120回第209回第210回第211回に続き、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 -f スクリプトファイル *.txt」あるいは「awk 'コマンド' *.txt」のようにファイル名を指定します。

 処理中のファイルの名前をawkスクリプト内部から参照するには、変数FILENAMEを使います。

 画面1のawkコマンドでは「*.txt」を読み出し、ファイル名と1行目の内容を出力しています。if文(第120回)で使用している変数FNRは、各ファイル中のレコード番号を表しています。

 比較のため、全ファイルを通した行数を保存しているNRの値も一緒に出力しました。

コマンド実行例

awk '{if (FNR==1){print FILENAME, FNR, NR, $0}}' *.txt

(複数ファイルに対して、ファイル名と1行目の内容などを出力する)


画面1 画面1 awkスクリプトで複数のファイルを読み出して一部の内容を選んで出力したところ


複数のファイルを処理した場合のBEGIN、ENDの扱い

 awkスクリプトで用いる「BEGIN」と「END」(第117回)には、対象となるデータの処理を開始する前と後に実行したい内容をそれぞれ指定します。

 ファイルが複数ある場合は、ファイルごとにBEGINとENDを実行するのではなく、全ファイルの処理を始める前と終わった後に実行します。

 以下のスクリプト(head3.awk)は、ファイル名と各ファイルの先頭3行を行番号付きで出力します。headコマンド(第3回)同様、ファイルとファイルの間には空行を入れるようにしました。

 BEGINとENDの動作を確認するために、BEGINブロックでは「BEGIN」と出力し、ENDブロックで「END」と出力しています。

BEGIN {
  print "BEGIN"		#「BEGIN」と出力
}
{
  if (FNR == 1){	#ファイルの先頭行の場合
    if (NR != 1){	#トータル行数が1ではない場合(2つ目以降のファイル)
      print ""		#空行を出力
    }
    print "==>",FILENAME, "<=="	#「 ==> ファイル名 <==」を出力
  }
  if (FNR <= 3){	#3行目以内の場合
    print FNR,NR,$0	#行番号、トータルの行番号、行の内容を出力
  }
}
END {
  print "END"		#「END」と出力
}
head3.awk
画面2 画面2 複数のファイルを処理し、BEGINとENDの扱いを確認したところ


ファイルに出力する

 awkのprint文では、コマンドラインと同じようにリダイレクトを利用できます。例えば、「{print $0 > "ファイル名"}」なら、1行(1レコード)全体をそのまま指定したファイルに出力します。

 ただし、結果を全て1つのファイルだけに出力するのであれば、awkの処理全体をリダイレクトでファイルに保存する方がスクリプトが単純になるでしょう。

 図3の2つのコマンドラインは、どちらも「ls *.txt」の結果を行番号付きでfile.lstというファイルに出力しています。

コマンド実行例

ls *.txt | awk '{print FNR, $0}' > file.lst

(ls *.txtの結果を行番号付きでファイルに出力する)

ls *.txt | awk '{print FNR, $0 > "file.lst"}'

(ls *.txtの結果を行番号付きでファイルに出力する)


画面3 画面3 ls *.txtの結果を行番号付きでファイルに出力する2つの方法


複数のファイルに出力する

 print文を使って出力先のファイルを指定すると、awkの処理内容を複数のファイルに分けて出力できます(画面4)。

 以下のtotal2.awkは、第211回のtotal.awkを一部変更したものです。order.txtを処理する前提のスクリプトです。

 count[品名]には各品物(A〜D)の個数を、total[品名]には各品物の金額を集計しています。

 ENDブロックでは変数fnに品名を表す1文字(A〜D)を入れた後、fnの内容の末尾に「.txt」という文字列を付けています。「sub(/$/, ".txt", fn)」という部分の処理です(※1)。

※1 行末($)だけを処理対象としているため文字列操作用のsub関数を利用した。この場合、gsub関数を使っても同じ結果になる(文字列を置き換える(第120回))。



BEGIN{
  RS=""		#レコードの区切りを空行とする
  FS="\n"	#フィールドの区切りを改行とする
}
{
  for (i=1;i<=NF;i++){
    split($i,d,"\t")		#各行(フィールド)をタブ区切りで分割して配列dにセット
    if (d[1] == "注文"){	#「注文」の場合
      count[d[2]] += d[4]	#品物(注文行の2列目)ごとの個数を加算
      total[d[2]] += d[3]*d[4]	#品物(注文行の2列目)ごとの合計金額を加算
    }
  }
}
END{
  for (i in count){
    fn = i	#ファイル名用の変数fnにi(品名)をセット
    sub(/$/, ".txt", fn)	#fnを「品名.txt」にする
    print "個数", count[i] > fn
    print "金額", total[i] >> fn
  }
}
total2.awk
東川 雄一
注文	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(注文とある行は、品物の種類(A〜D)、単価、個数を記録している。表示の都合上ブロックを区切る改行の代わりにタブを入れた)
画面4 画面4 awkスクリプトの処理結果を複数のファイルに出力したところ 品名(A〜D)ごとに異なるファイルに集計結果を分けた。


筆者紹介

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

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のメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。