本連載は、Linuxのコマンドについて、基本書式からオプション、具体的な実行例までを紹介していきます。今回は、テキストのパターン処理を行う「awk」コマンドです。
本連載は、Linuxのコマンドについて、基本書式からオプション、具体的な実行例までを紹介していきます。今回はテキストのパターン処理を行う「awk(gawk)」コマンドです。
連載第115回、第116回、第117回、第118回、第119回、第120回に続き、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 [オプション] [コマンド] [ファイル……]
※[ ]は省略可能な引数を示しています
短いオプション | 意味 |
---|---|
-f ファイル名 | awkスクリプトが書かれたファイルを指定する |
-F 区切り文字 | 区切り文字を指定する(デフォルトは空白文字) |
-v 変数名=値 | 変数を定義する |
※ gawk(GNU版awk)の場合、長いオプションも使用可能。-fは--file program-file、-Fは--field-separator、-vは--assign。gawkにはこの他にも多数のオプションがある。
変数名 | 意味 |
---|---|
ARGC | 引数の個数 |
ARGV | 引数(配列) |
ENVIRON | 環境変数を収めた連想配列。例えば環境変数LANGならばENVIRON["LANG"]と参照できる |
FILENAME | 現在処理しているファイルの名前 |
FNR | 現在処理しているファイルのレコード番号(処理しているファイルが1つの場合はNRと同じ値になる) |
FS | フィールドの区切り文字(-Fオプションで変更可能、デフォルトはスペース) |
NR | 現在処理しているレコード番号(行番号) |
OFS | 出力時のフィールドの区切り(デフォルトは空白) |
ORS | 出力時のレコードの区切り(デフォルトは改行) |
RS | レコードの区切り(デフォルトは改行) |
関数(引数) | 意味 |
---|---|
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の上半分に示したデータ(sample1.txt)があったとき、各都道府県が何回登場したのかを調べたいとしましょう。この場合、以下のようなスクリプトファイル(sample1.awk)を使って処理できます。
{ #全ての行に対して行う処理 count[$1]++ #count[都道府県名]を1増やす } END{ #全ての行を読み終わった後で行う処理 for(name in count){ print name,count[name] } }
このスクリプトは大きく2つのブロック({……})からできています。最初のブロックでは、countという配列を使い、東京と書いてある行を読み込んだら、count["東京"]を1増やす、神奈川を読み込んだらcount["神奈川"]を1増やす……という処理で、各都道府県の出現回数を数えています。
countの添え字を表す「$1」には各行の1つ目のフィールドが入ります。このため、それぞれの都道府県を「count[$1]++」で数えることができます(※1)。なお「++」は1増やすという意味の演算子です(第119回)。
※1 sample1.txtの場合は、1行に1フィールドしかないため、読み込んだ行全体を表す「$0」を使って、「count[$0]++」としても結果は同じになる。
続くENDブロックでは、for文を使ってcount配列の内容を全て出力しています(第117回)。for文で配列を参照する場合は「for (変数名 in 配列名) 処理」のように指定します。
ここでは、nameという変数に、count配列に保存されている都道府県名をセットし、count[name]で配列の値を出力しています。
画面1の下半分では、sample1.awkと同じ内容をコマンドラインで直接記述しました。記述内容は「awk '{count[$1]++}END{for(name in count)print name,count[name]}'」です(※2)。
※2 for文で実行しているのは、print文1つだけなので{}を省略した。
cat ファイル名 | awk '処理内容'
(ファイルの内容をawkコマンドに送り、処理を進める)
cat ファイル名 | awk -f awkファイル名
(ファイルの内容をawkコマンドに送る。処理内容はawkファイルに書かれている)
先ほどと同じ要領で、都道府県名と数値からなるデータを集計してみましょう。画面2の上半分にあるデータ(sample2.txt)が対象です。今回は次のようなスクリプトファイル(sample2.awk)を使います。画面2の下半分では、sample2.awkと同じ内容をコマンドラインで直接記述しました(※3)。
※3 コマンドラインの指定内容は以下の通り。「awk '{count[$1]++;sum[$1]+=$2}END{for(name in count)print name,count[name],sum[name]}'」
{ count[$1]++ #count[都道府県名]を1増やす sum[$1]+=$2 #sum[都道府県名]に2番目のフィールドの値を加算する } END{ for(name in count){ print name,count[name],sum[name] } }
画面1や画面2にはデータ以外のテキストが含まれていませんでした。しかし、画面3の上部に示したように、見出しが入っているテキストでは、この部分を取り除く処理が必要です。こうしないと、画面3の中央にあるように集計データに「地名 1 0」という無意味な結果が混ざってしまいます。
awkコマンドが備えるdelete文を使うことで、配列から不要な要素を削除できます。画面3では、見出しを「count["地名"]」としてカウントしているので、「delete count["地名"]」で削除します(※4)。
画面3の下部では、sample3.awkと同じ内容をコマンドラインで直接記述しました(※5)。
※4 配列sumも「sum["地名"]」でゼロと集計されている。今回のケースでは、for文で配列countを使って出力する要素を決めているため「delete sum["地名"]」の処理は省略した。
※5 コマンドラインの指定内容は以下の通り。「awk '{count[$1]++;sum[$1]+=$2}END{delete count["地名"];for(name in count)print name,count[name],sum[name]}'」
{ count[$1]++ sum[$1]+=$2 } END{ delete count["地名"] #count["地名"]を削除する for(name in count){ print name,count[name],sum[name] } }
なお、delete文を使わず、集計時に先頭行を対象外にすることもできます。最初の処理の頭に「(NR>1)」を付けて「(NR>1){count[$1]++;sum[$1]+=$2}」のようにするとよいでしょう。これは2行目以降を処理の対象とするという意味です。
(NR>1){ #2行目以降を対象とする count[$1]++ sum[$1]+=$2 } END{ for(name in count){ print name,count[name],sum[name] } }
集計後に配列から不要な要素を削除するのではなく、集計前に見出しに含まれる文字列(今回のケースでは"地名")を含む行を対象外とすることもできます。「!/地名/{count[$1]++;sum[$1]+=$2}」のようにします。
!/地名/{ #"地名"を含まない行を対象とする count[$1]++ sum[$1]+=$2 } END{ for(name in count){ print name,count[name],sum[name] } }
西村 めぐみ(にしむら めぐみ)
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.