awkコマンドとは
awk(オーク)はテキストを1行ずつ読みながら、1行をくつかの列(フィールド)に分けて読む・抜き出す・加工するためのコマンド。
たとえば、ログの「3列目だけ取りたい」時にawkが便利。
awkの基本構文
awk の基本構文は以下。
awk 'パターン { アクション }' ファイル名
パターン=どんな行を選ぶか
アクション=その行で何をするか
ざっくり言うと 「ファイルの中で、〇〇な行があったら、△△をする」。
awkの基本をサンプルから覚える
サンプル
サンプルとして使用するファイルの内容。
各行は「名前・年齢・都市・職業」の4つのフィールドで構成されている。
# sample.txt
Alice 25 Tokyo Engineer
Bob 30 Osaka Manager
Charlie 28 Nagoya Designer
David 35 Fukuoka Engineer
Eve 22 Tokyo Intern
サンプルファイルの分解
タブやスペースごとに awk が自動で以下表のように読み込んでくれる。
| フィールド番号 | 内容(意味) | awkで書くと |
|---|---|---|
$1 | 名前(Name) | Alice |
$2 | 年齢(Age) | 25 |
$3 | 都市(City) | Tokyo |
$4 | 職業(Job) | Engineer |
実際に動かしてみる
行全体を表示
print で出力し、$0 でその行全体(すべてのフィールド)を指定。
$ awk '{print $0}' sample.txt
ファイル全体を見たいなら cat でいい気がするので、実務では使わないかも。
# sample.txt
Alice 25 Tokyo Engineer
Bob 30 Osaka Manager
Charlie 28 Nagoya Designer
David 35 Fukuoka Engineer
Eve 22 Tokyo Intern
1列目(名前)だけ表示
$1 で名前フィールドを出力。
$ awk '{print $1}' sample.txt
これだけだとコメント(#)まで出力されてしまうので、実務ではもう少し条件を絞った方がよさそう。
#
Alice
Bob
Charlie
David
Eve
コメント(#)を除外
! = ~でない、/文字列/ = 正規表現の始まりと終わりを示す囲み記号、^ = 先頭行 の意味。
先頭行がコメント(#)でない名前フィールドを出力。
$ awk '!/^#/ {print $1}' sample.txt
コメント(#)が除外され、名前のフィールドだけが出力される。
Alice
Bob
Charlie
David
Eve
名前と都市を表示
$1 で名前フィールド、$3 で都市フィールドを指定。
$ awk '{print $1, $3}' sample.txt
それぞれのフィールドが正しく抽出されている。
#
Alice Tokyo
Bob Osaka
Charlie Nagoya
David Fukuoka
Eve Tokyo
Tokyo に住んでいる人だけ表示
$3 を == で Tokyo 指定。
$ awk '$3 == "Tokyo" {print $0}' sample.txt
Tokyo に住んでいる人だけが抽出される。
Alice 25 Tokyo Engineer
Eve 22 Tokyo Intern
年齢が30歳以上の人だけ表示
$2 >= 30 で指定。ついでにコメントも && でつなげて除外しておく。
$ awk '!/^#/ && $2 >= 30 {print $1, $2}' sample.txt
30歳以上の人だけが抽出される。
Bob 30
David 35
Engineerの人数を数える
$4 == “Engineer” で エンジニアを指定、{count++} で条件が真なら count を 1 増やす、END {print count} でファイルを全部読み終わったあとに出力。
$ awk '$4 == "Engineer" {count++} END {print count}' sample.txt
きちんとカウントされている。
2
平均年齢を計算
{sum += $2; count++} で 2 列目(年齢)の値を合計し、行数を 1 増やす。
END {print sum/count} でファイルをすべて読み終えたあとに平均を出力する。
※今回はカウント処理を行うため、コメント(#)のスキップが必須。
; は改行と同じ意味。
$ awk '!/^#/ {sum += $2; count++} END {print sum/count}' sample.txt
平均年齢が出力される。
28
このコマンドの動きは以下の通り。
| 行 | $2(年齢) | 条件 !/^#/ | sumの値 | countの値 |
|---|---|---|---|---|
| # sample.txt | – | False | 0 | 0 |
| Alice 25 Tokyo Engineer | 25 | True | 25 | 1 |
| Bob 30 Osaka Manager | 30 | True | 55 | 2 |
| Charlie 28 Nagoya Designer | 28 | True | 83 | 3 |
| David 35 Fukuoka Engineer | 35 | True | 118 | 4 |
| Eve 22 Tokyo Intern | 22 | True | 140 | 5 |
都市ごとの人数を集計
{city[$3]++} で 3 列目(都市名)をキーとしてカウントする。
END { … } はファイルをすべて読み終えたあとに 1 回だけ実行される処理。
for (c in city) ですべての都市(キー)を順に処理し、
print c, city[c] で都市名と人数を出力する。
※今回はカウントをしているのでコメントのスキップは必須。
$ awk '!/^#/ {city[$3]++} END {for (c in city) print c, city[c]}' sample.txt
都市ごとの人数が出力される。
Osaka 1
Fukuoka 1
Nagoya 1
Tokyo 2
このコマンドの動きは以下の通り。
| 行 | $3(都市) | 条件 !/^#/ | city[$3] の動作 | city配列の内容(内部状態) |
|---|---|---|---|---|
| # sample.txt | ― | False | 実行されない | (何もない) |
| Alice 25 Tokyo Engineer | Tokyo | True | city["Tokyo"]++ → 1 | Tokyo:1 |
| Bob 30 Osaka Manager | Osaka | True | city["Osaka"]++ → 1 | Tokyo:1, Osaka:1 |
| Charlie 28 Nagoya Designer | Nagoya | True | city["Nagoya"]++ → 1 | Tokyo:1, Osaka:1, Nagoya:1 |
| David 35 Fukuoka Engineer | Fukuoka | True | city["Fukuoka"]++ → 1 | Tokyo:1, Osaka:1, Nagoya:1, Fukuoka:1 |
| Eve 22 Tokyo Intern | Tokyo | True | city["Tokyo"]++ → 2 | Tokyo:2, Osaka:1, Nagoya:1, Fukuoka:1 |
疑問に思ったこと
文章ベースのファイルにはawkコマンドは使えないのか
たとえば、
Aliceは東京で働いています。
Bobは大阪に住んでいます。
Charlieは28歳です。
という文章ファイルがあった場合、awk の使いどころがあるのか気になった。
awk の動きとしては「スペースやタブで明確に区切られていない」文章だと、
$1 Aliceは東京で働いています。
$1 Bobは大阪に住んでいます。
$1 Charlieは28歳です。
となり、すべて $1 になるのでフィールド分けができず、awk の長所は活かせない。
一応、grep のように抽出することは可能。
$ awk '/東京/ {print $0}' sample_text.txt
Aliceは東京で働いています。
$ awk '/[0-9]+歳/ {print $0}' sample_text.txt
Charlieは28歳です。
行番号を付けることもできる。
$ awk '{print NR, $0}' sample_text.txt
1 Aliceは東京で働いています。
2 Bobは大阪に住んでいます。
3 Charlieは28歳です。
結論としては、awk でも文章ファイルの操作はある程度できるけど、本来の用途とは離れており、わざわざ awk で文章ファイルを操作する必要はなさそう。
コメント