awkコマンド|必要な列だけを取り出して数えるまでの書き方
awk コマンドを調べる場面は、たいてい決まっています。ログや書き出したCSVを前にして、3列目だけが欲しい、条件に合う行だけ数えたい、という状態です。grep は行を選べても列を落とせず、表計算に取り込むには量が多すぎる。その隙間を埋めるのが awk で、覚える文法は驚くほど少なく済みます。ここでは Mac に最初から入っている awk のマニュアルに書かれている範囲で、列の取り出しから集計までと、日本語を含むテキストで先に知っておきたい制限を整理します。
テキスト処理そのものが作業効率に直結する、という前提はどの解説でも共通しています。
Linuxでの作業を効率化するうえで、テキスト処理は欠かせません。その中でも特に便利なツールが「awkコマンド」です。 出典: envader.plus
Macに入っているawkは、解説記事のawkと同じではない
最初に確かめておくと後で混乱しないのが、いま手元で動く awk がどれなのか、という点です。Web上の解説はGNU版のawk(gawk)を前提にしたものが多く、Macに標準で入っているawkはそれとは別の系統です。マニュアルの表題は「pattern-directed scanning and processing language」で、参考文献にはAho、Kernighan、Weinbergerによる1988年の書籍が挙げられています。つまり元祖のawkの系統であり、GNU版で追加された拡張は前提にできません。
書式はマニュアルの冒頭に1行で示されています。
awk [ -F fs ] [ -v var=value ] [ 'prog' | -f progfile ] [ file ... ]
この4つで全部です。-F が区切り文字、-v が実行前に渡す変数、-f がプログラムを書いたファイル、残りが対象のファイルです。ファイル名の代わりに - を書くと標準入力を読みます。var=value の形の引数はファイル名ではなく代入として扱われ、その位置に来たときに実行されます。
そして、日本語を扱う人が先に知るべき記述が、マニュアルの最後のBUGSの節にあります。8ビットの文字集合しか正しく扱えない、と明記されています。1文字が複数バイトになる日本語を length で数えたり substr で切り出したりする処理は、この記述の外側にあるということです。区切り文字がカンマやタブで、取り出したい列の中身が数字や英数字であれば問題は起きません。日本語そのものを文字単位で数えたり切ったりする用途では、awkを選ぶ前に立ち止まる価値があります。
覚える文法は「パターン { 動作 }」の1つだけ
awkのプログラムは、次の形の文を並べたものです。
pattern { action }
動作を省くとその行を表示し、パターンを省くとすべての行に当たります。この2つの省略規則が、awkが短く書ける理由です。{ print $1 } はパターンを省いた形なので全行に適用され、/error/ は動作を省いた形なので該当行をそのまま表示します。文は改行かセミコロンで区切ります。
読み込まれた1行は、既定では空白で区切られた列に分けられ、それぞれ $1、$2 と数えられます。$0 は行全体を指します。列の数は NF に入るので、最後の列は $NF で取れます。区切り文字を変えたいときは -F を使い、ここに指定するのは文字ではなく正規表現です。たとえばカンマの後ろに空白が混ざるかもしれない入力なら、BEGIN { FS = ",[ \t]*|[ \t]+" } のように1つの正規表現で両方を吸収できます。この書き方はマニュアルの例示にそのまま載っています。
覚えておくと効く変数は6個です。NR が読み込んだ行の通し番号、FNR がファイルごとの行番号、FILENAME が現在のファイル名、NF が列数、OFS が出力時の列の区切り(既定は空白)、ORS が出力時の行の区切り(既定は改行)です。複数のファイルをまとめて処理して、どのファイルの何行目かを出したいときは FILENAME と FNR の組み合わせになります。
列を並べ替える、桁をそろえる
実務でいちばん多いのは、列の取り出しと並べ替えです。マニュアルの例示にある2つは、そのまま使える最小形です。
awk '{ print $2, $1 }' file
awk 'length($0) > 72' file
上は1列目と2列目を入れ替えて出し、下は72文字を超える行だけを表示します。下の例はパターンだけを書いた形なので、動作を書かなくても行がそのまま出ます。
print は引数を OFS で区切り、ORS で終端して標準出力に書きます。> file や >> file を付ければファイルへ、| cmd を付ければパイプへ流せます。桁をそろえたいときは printf を使い、書式はC言語の printf と同じものが使えます。
ここで1つ落とし穴があります。awkが数値として扱った値を print で出すと、既定の書式 %.6g で整形されます。有効桁が6桁なので、注文番号やIDのような桁数の多い数値を計算に通してから表示すると、指数表記に化けることがあります。桁を保ちたいときは printf "%d\n" のように明示するか、そもそも計算に通さず文字列として扱うのが確実です。この既定値は OFMT と CONVFMT という変数に入っているので、必要なら書き換えられます。
数える、合計する、キーごとに集計する
awkが表計算の代わりになるのは、BEGIN と END という2つの特別なパターンがあるからです。BEGIN は最初の行を読む前、END は最後の行を読み終えた後に実行されます。マニュアルの例示にある合計と平均は、この仕組みの典型です。
awk '{ s += $1 } END { print "sum is", s, " average is", s/NR }' file
1行ごとに1列目を足し込み、最後に合計と、行数で割った平均を出します。変数は宣言せずに使えて、初期値は空文字列です。数値として足し込めば0から始まります。
もう一段だけ進めると、awkは「キーごとの集計」までこなします。配列の添字には数値だけでなく任意の文字列が使えるので、列の値をそのまま添字にして数を数えられます。
awk -F, '{ c[$3]++ } END { for (k in c) print k, c[k] }' data.csv
3列目の値ごとに件数を数えて出す、という処理がこれだけで済みます。for (var in array) は添字を順に取り出す構文で、出てくる順番は決まっていないため、順序が必要なら sort に流します。要素を消したいときは delete c[k]、全部消すなら delete c です。
条件を足すのも同じ行の中で書けます。$2 > 1000 { n++ } のように比較を書けばその行だけ数えられ、$1 ~ /^2026-09/ のように ~ を使えば正規表現との照合になります。一致しない行を選ぶなら !~ です。
範囲で切り出す、複数ファイルをまとめる
ログを読むときに効くのが、2つのパターンをカンマでつないだ範囲指定です。マニュアルには次の例が載っています。
awk '/start/, /stop/' file
start が現れた行から stop が現れた行までを表示します。エラーの前後だけ抜き出したいときに、行番号を数える手間が消えます。
処理を途中で打ち切る書き方も用意されています。next はその行の残りのパターンを飛ばして次の行へ進み、nextfile は現在のファイルを読むのをやめて次のファイルの先頭へ移ります。exit はその場で終了し、exit 1 のように書けば終了ステータスも渡せます。ログの先頭数十行だけを見たい、という用途では NR > 100 { exit } が最も速い書き方になります。
関数も定義できます。パターンを書く位置に function foo(a, b, c) { ... return x } と書く形です。引数のうちスカラーは値で渡され、配列名は参照で渡されます。注意したいのは変数の扱いで、引数として書いたものだけが関数の中のローカル変数になり、それ以外はすべてグローバルです。ローカル変数が欲しければ、呼び出しでは渡さない余分な引数を定義に書き足す、という作法になります。マニュアル自身がこの仕様を率直に不出来だと書いているくらいなので、長い関数を書くよりも、短い awk を並べてパイプでつなぐ方が読みやすく仕上がります。
型が曖昧なせいで起きる間違いと、その避け方
awkの値は、文脈に応じて数値と文字列のどちらとしても振る舞います。便利な反面、ここが間違いの入り口になります。マニュアルは変換に明示的な手段がないことを認めた上で、回避策を2つ挙げています。数値として扱わせたいときは + 0 を足し、文字列として扱わせたいときは "" を連結することです。
具体的に困るのは比較のときです。$1 == 10 は、$1 が "10" でも "10.0" でも数値として一致しますが、"010" のようにゼロ埋めされた値と文字列として比べると一致しません。入力の形が揺れる可能性があるなら、$1 + 0 == 10 と書いて意図を固定します。逆に、先頭のゼロを保ったまま突き合わせたいなら $1 "" == "010" の形になります。
もう1つ、sub と gsub の置換文字列の中で、バックスラッシュとアンパサンドをどう解釈するかという細かい違いがあります。マニュアルによると、環境変数 POSIXLY_CORRECT が設定されているときはPOSIXの規則に従います。設定の有無で結果が変わる可能性があるので、置換にアンパサンドを含める処理を書くときは、手元とサーバの両方で同じ結果になるかを確かめる価値があります。
シェルの変数をawkの中へ渡すときは -v を使います。プログラムの実行前に代入が済むため、BEGIN の中からでも参照できます。シングルクォートで囲んだプログラムの中にシェルの変数を直接書き込む方法もよく見かけますが、値に空白や引用符が混ざった瞬間に壊れるため、-v name=value の形で渡すほうが事故が起きません。プログラムが数行を超えるようになったら、-f でファイルに分けて、そのファイルを版管理に入れる段階です。
外部コマンドを呼ぶ system(cmd) も用意されていて、戻り値は終了ステータスです。エラーなら -1、シグナルで落ちた場合は 256 + シグナル番号 という具合に、区別できる形で返ります。ただしawkの中から外部コマンドを呼び始めると、シェルスクリプトとしても awk スクリプトとしても読みにくくなります。呼び出しが2つ以上必要になった時点で、書く場所を変える合図と考えるのが無難です。
フォルダとターミナルとAIを同じ窓で扱う場合の位置づけ
awkが速いのは、対象のファイルがどこにあるかを既に知っている場合です。実際の作業でかかる時間は、awkを書く数十秒ではなく、その前段にあります。目的のファイルがどのフォルダにあるかを探し、パスを確かめ、ターミナルへ持っていき、出てきた結果をまた別の窓で確認する。この往復のほうが長いという状態は珍しくありません。
その前提で見ると、ファイル管理側で潰せる手間と、awk側で潰せる手間は別のものだと分かります。フォルダの一覧と同じ画面でコマンドを打てる構成についてはできることに整理があり、2画面表示や転送用のツールとの違いは他のファイル管理との比較で並べて説明されています。手元を離れていてもコマンドの結果を確かめたい場合は、iPhone・iPadから続きをが該当する話です。
もう1つ、awkでは解決しない類の仕事も切り分けておくと迷いません。awkが得意なのは規則が決まっている処理で、規則そのものを中身を読んで決める仕事は苦手です。スキャンした書類の名前を内容から決める、といった作業は、判断が本体なので awk では書けません。導入や費用の前提は料金、動かし始めてから出てくる疑問はよくある質問にまとまっています。
手元のawkでどこまでできるかを確かめる最短の道は、man awk を開いてEXAMPLESとBUGSの2つの節だけ読むことです。前者は5つの実例、後者は避けるべき使い方で、この2つだけで判断材料はそろいます。
よくある質問
MacのawkとLinuxのawkは同じものですか?
同じではありません。Macに標準で入っているawkは元祖のawkの系統で、マニュアルの参考文献にはAho、Kernighan、Weinbergerの1988年の書籍が挙げられています。Web上の解説はGNU版(gawk)前提のものが多く、GNU版の拡張機能はそのままでは動きません。手元の仕様を確かめるには man awk のBUGSの節まで読むのが確実です。
awkで日本語のテキストを扱っても大丈夫ですか?
区切り文字で列に分けて取り出すだけなら問題ありません。マニュアルのBUGSの節には8ビットの文字集合しか正しく扱えないと書かれているため、日本語を length で文字数として数えたり substr で切り出したりする処理は想定の外です。文字単位の加工が必要なら、別の道具を選ぶほうが安全です。
grepやsedとどう使い分けますか?
行を選ぶだけならgrep、行の中の文字を置き換えるだけならsedで足ります。awkが必要になるのは、列を指定して取り出す、列の値で条件を書く、読みながら数えたり合計したりする、のいずれかが入ったときです。1つのawkで無理に書かず、パイプでつないで役割を分けたほうが後から読めます。
桁の多い数値が指数表記になってしまうのはなぜですか?
awkが数値として扱った値を print で出すと、既定の書式 %.6g で整形されるためです。有効桁が6桁なので、IDのような長い数値は丸められて表示されます。桁を保つには printf "%d\n" のように書式を明示するか、OFMT と CONVFMT を書き換えるか、計算に通さず文字列として扱ってください。