🐧 Linux 総合学習プラットフォーム
正規表現・テキスト処理 ・ 中級

awk でフィールド処理する

awk は行を空白で区切られた「フィールド」に分けて扱える、表形式テキスト向けのツールです。各列は $1 $2 … で参照でき、$0 は行全体、NF は列数、NR は行番号を表します。awk '{print $1}' なら各行の1列目だけを出力します。awk -F: '{print $1}' のように -F で区切り文字を変えれば、コロン区切りの /etc/passwd なども処理できます。awk '$3 > 100' のように条件で行を絞ったり、合計を計算したりと、cut より柔軟な列処理が得意です。

grep が行を探し、sed が行を加工する道具だとすれば、awk は行を「列」に分けて扱う道具だ。

スペースやタブで区切られた表形式のテキストを、列ごとに取り出したり、値で条件を付けたり、合計や平均を計算したりできる。名前は作者3人の頭文字に由来し、実体は小さなプログラミング言語だ。

🔗
たとえawk は、表を縦の列で切り分ける道具だ。grep が横の行を選ぶのに対し、awk は各行を列に割って、狙った列だけを取り出せる。

基本の形は awk 'パターン{アクション}' ファイル で、「条件に当たる行に対して、{ } の中の処理を行う」と読む。パターンを省けば全行が対象になり、アクションを省けば一致した行をそのまま出すなど、両者を組み合わせて挙動を決める。

列を切り出すだけなら cut でも済むが、列の値で条件を書いたり計算したりできる点で、awk は一段柔軟だ。

🧱 行を「列」に分けるとは、どういうことか

awk は各行を、空白を区切りとして自動的に列に分解する。この1つ1つの列をフィールドと呼び、先頭から $1 $2 $3 … というフィールド変数で取り出せる。

root : x : 0 : 0 : root : /root : /bin/bash$1root$2x$30$NF/bin/bashawk -F: で : 区切り。$0 は行全体、$NF は最後の列NR は行番号、NF は列数

$0 は行全体を表し、$NF は「最後の列」を指す。たとえば awk '{print $1}' なら各行の1列目だけを縦に並べて出力する。

awk -F: '{print $1}' /etc/passwd で、コロン区切りの1列目(ユーザー名)だけを取り出せる。$0 は行全体、$NF は最後の列。

なお既定の空白区切りは賢く、スペースやタブが連続していても1つの区切りとして扱い、行頭の空白も無視してくれるので、桁をそろえて整形された表でも素直に列を取れる。

区切り文字を空白以外に変えたいときは -F オプションを使い、awk -F: '{print $1}' /etc/passwd のように書けば、コロン区切りの /etc/passwd からユーザー名(1列目)だけを取り出せる。

print $1, $NF のようにカンマで区切ると、間にスペースを入れて並べてくれる(root /bin/bash のように)。

🔢 行番号や列数を、その場で知るには

awk には便利な組み込み変数がある。NR はこれまでに読んだ行数、すなわち現在の行番号を表し、NF はその行の列数(フィールド数)を表す。

printf 'a b c\nx y\n' | awk '{print NR, NF, $0}' を実行すると、1行目は「1 3 a b c」、2行目は「2 2 x y」と出て、行番号・列数・行全体がひと目で分かる。grep -n 風の行番号付けも awk '{print NR, $0}' で作れる。

printf 'a b c\nx y\n' | awk '{print NR, NF, $0}' は「1 3 a b c」「2 2 x y」と出る。NR は行番号、NF は列数だ。

$NF は列数が行ごとに違っても必ず最後の列を指すので、列の総数が一定でないデータでは、番号で $7 と決め打ちするより $NF を使うほうが安全だ。応用として $(NF-1) と書けば「最後から2番目の列」を指せる。

コツ列の総数が一定でないデータでは、$7 と決め打ちするより $NF(最後の列)を使うほうが安全。$(NF-1) なら最後から2番目を指せる。

なお NR を条件に使うと行の範囲も選べ、awk 'NR>=2 && NR<=4' file は2行目から4行目だけを、awk 'NR%2==0' file なら偶数行だけを出力できる。

フィールドと行番号という2つの軸で行と列を自在に指せるのが、awk の見通しのよさだ。

⚖ grepと何が決定的に違うのか

awk の最大の強みは、列の値そのものを条件にして行を絞り込めることだ。{ } の前に書いた「パターン」が条件として働く。

awk -F: '$3 >= 1000 {print $1, $3}' /etc/passwd は、3列目(UID)が1000以上の行、つまり一般ユーザーだけを抜き出す。

💡
ポイントgrep が「文字のパターンで行を探す」のに対し、awk は $3 >= 1000 のように数値の大小や比較で行を選べる。これが本質的な違いだ。

grep が「文字のパターンで行を探す」のに対し、awk は「$3 >= 1000」のように数値の大小や比較で行を選べるのが本質的な違いだ。条件には文字列の一致や正規表現($1 ~ /^web/ のような書き方)も使えるので、表形式データの絞り込みでは grep より一段踏み込んだことができる。

パターンを省略すると、すべての行がアクションの対象になる。

➕ 合計や平均は、どこで計算するのか

awk には、処理の前後に1回だけ走る特別なブロックがある。BEGIN は全行を読む前に1回、END はすべて読み終えた後に1回実行される。これを使うと集計が書ける。

printf '10\n20\n30\n' | awk '{sum += $1} END {print "合計", sum, "平均", sum/NR}' は、各行で sum に値を足し込み、最後に END で結果を1回だけ出す。このとき NR は最終的に全行数になっているので、sum/NR が平均になる(この例では「合計 60 平均 20」)。

printf '10\n20\n30\n' | awk '{sum += $1} END {print "合計", sum, "平均", sum/NR}' は「合計 60 平均 20」を出す。END は全行を読んだ後に1回走る。

BEGIN は逆に、見出し行の出力や、区切り文字・変数の初期値の設定に使う。

1行ずつの処理と、最後にまとめる処理を分けて書けるのが awk の集計の型だ。

⚠ つまずいたら、まずこれ

見た目を整えて表にしたいときは printf を使う。printf 'apple 120\nbanana 80\n' | awk '{printf "%-8s %5d\n", $1, $2}' のように書式を指定でき、%-8s は左寄せ8桁の文字列、%5d は右寄せ5桁の整数を表す。

print と違って printf は自動で改行しないため、末尾に \n を自分で書く必要があり、これを忘れて出力が1行につながってしまうのがよくある失敗だ。

つまずきprint と違い printf は自動で改行しない。末尾に \n を自分で書かないと、出力が1行につながってしまう。

また、区切りが空白でないのに -F を指定し忘れて列がうまく取れない、というのも頻出だ。

もう一つ注意したいのは、awk のプログラムは多くの記号を含むためシングルクォートで囲むのが鉄則だという点で、ダブルクォートで囲むと $1 などがシェル変数として展開されてしまい、まったく違う動きになる。

つまずきawk のプログラムはシングルクォートで囲むのが鉄則。ダブルクォートだと $1 などがシェル変数として展開され、まったく違う動きになる。

実務では、awk は /etc/passwd のようなコロン区切りファイルの解析、ログの特定列の集計、サイズや件数の合計といった場面で活躍する。

「列を取り出すだけなら cut、列の値で条件を付けたり計算したりするなら awk」と使い分けると、テキスト処理の幅が大きく広がる。フィールドという考え方さえ身につけば、表形式のデータはほとんど awk で思いどおりに扱えるようになる。

この項目に出てくる用語

awkおーく
行を列(フィールド)に分けて処理するテキスト処理言語。
フィールドふぃーるど
1行を区切り文字で分けた1つ1つの列。

関連コマンド

awk

▶ 学習アプリでこの続きを学ぶ・演習する