実用 awk ワンライナー

  • 283
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

AWK(オーク)を使ったワンライナーはとても強力で簡便なテキスト処理を可能とします.最近は,Perl や Ruby でもほとんど同様のことができ,取って代わられて久しいですが未だにその枯れ力といいますか,汎用性に関しては群を抜いているように思います.

また,awk とは AWK の処理系のことを指しています.
一般に元祖 awk と呼ばれる処理系が最初と言われています.実際はわかりませんが,絶滅種の如く見かけることはなく,その文法などに関しても関数定義などの基本的な部分が抜けていたので New awk(通称,nawk)として多少の文法が追加された処理系が,その流派を継いでいます.
そして GNU プロジェクトによって大幅強化された awk が gawk です.
よく見かけるのは,gawk か nawk のどっちかだと思います.

現在,Linux では標準で gawk が,OS X では 2007 年製の nawk が利用できるようです.

ライブラリについて

AWK はテキスト処理にも向きますが,統計処理のような使い方もできたりします.
今回,ワンライナーの方向性とはずれてしまうので,省略しますが調べてみるとかなりの数がヒットします.

また,AWK は有用なライブラリが少ないため,私用に自作したライブラリを紹介しておきます.これは Go 言語のパッケージ体系にヒントを得て,似せたような構造を持っています.テストも書いているため使い方は簡単に把握できると思います.まだまだ,発展途上のためバグフィックスやエンハンスメントなどの Pull Req いただけると嬉しいです.

ワンライナーについて

ワンライナーは基本的に短くてナンボです.よって,極力短くできるようにスペースなどは排除しています(最低限の見やすさは確保した上です).また,書き方が何通りかある場合,一番短いものを採用しました(他にいい方法・短い方法がある場合は編集リクエストかコメントでお願いします).加えて,以下で紹介するワンライナーすべては,パイプでの入力を期待した記述をしています.

最初の行を表示する

$ awk 'NR==1'

awk 'NR==1{print $0}' とするのが正規の書き方ですが,省略できます.NR は awk で使われる組み込み変数で,入力行の行番号がセットされます.
また,'{print $0}' 部分はアクションと呼ばれる部分で,その前部にあるパターンによって実行されるか否かが決まります.
ここらへんの詳しい説明は,awk 系の記事や書籍に回します.

特定の N 行を表示する

$ awk 'NR==N'

前項の応用です.N が行数以上の数字を指定すると何も表示されません.NR は入力ファイルの通し番号ですが,FNR はファイルごとの通し番号です.

最後の行を表示する

$ awk 'END{print}'

awk 'END{print $0}' の省略です.$0 にはレコード(入力行のこと)の全体が入っています.FS(組み込み変数でセパレータ)によって分割されたレコードが $1$2,… ,$NF に入っています.

空行を削除する

$ awk 'NF'
$ awk '$0'
$ awk '/./'

前項にもある通り,FS(組み込み変数でセパレータ)によって分割されたレコードが $1$2,… ,$NF に入っていますが,この個数が NF に入っています.
つまり,空行は NF が 0 となります.awk の場合,パターンが偽(0)のとき,省略された暗黙のアクション({print $0}は実行されません.
よって,空行部分を表示しないことができます.

awk '/./' でも同じく削除することができます.これは正規表現で一文字にマッチするパターンを指定しています(/./).空行では一文字もなくマッチしないので表示されません.また,awk '$0' でも同様です.後述しますが,AWK では空白は偽になります.$0 には行全体が入りますが,空行は空白です.その場合,表示されません.

文字数をカウントする(wc -c

$ awk '{n+=length($0)} END{print n}'

すべてのレコードのを length 関数でカウントし,足しあわせ,最後に END ブロックで表示します.

単語数をカウントする(wc -w

$ awk '{n+=NF} END{print n}'

組み込み変数 NF は組み込み変数 OFS によって区切られたレコード(フィールド)の数が入っています.

行数をカウントする(wc -l

$ awk 'END{print NR}'

NR は入力行の行番号です.END ブロックに記述すれば,最後の入力行の行番号を読み終わった NR にアクセスできるため最終行の行番号,つまり行数を表示できます.

行末の空白やタブを削除する

$ awk '{sub(/[ \t]+$/, "")}1'

$ で行末としていますが,^ などとすれば行頭にもでき,^|$ とすれば行頭と行末のホワイトスペース(空白とタブ)を削除できます.

Unix の改行コードに変換する

$ awk 'sub(/\r$/,"")'

改行コードは環境依存です.Windows では改行は CR と LF の組み合わせです.この例では,\r つまり CR を消し,LF(Linux などで使われる改行コード)に変換しています.詳しくは「改行コード」で検索してください.

Windows の改行コードに変換する

$ awk 'sub(/$/,"\r")'

前項の逆アプローチです.

逆順出力をする(tac

$ awk '{a[i++]=$0} END{for(j=i-1; j>=0;) print a[j--]}'

少し長くなりますが,入力の末尾から先頭に向かって表示するように仕向けているだけです.

重複するレコードを削除する(uniq

$ awk '!a[$0]++'

これは AWK の連想配列の特徴とパターンの真偽値をうまく扱ったトリッキーな記述をしています.連想配列に代入していき,重複しないとインクリメントされずに 0 になりますが,それを ! で反転させてそのときのみを取り出しています.

これは,ソートせずに一意な値をフィルター処理できるためとても便利です.-F オプションでデリミタ(区切り文字)を指定して,$0 でなく好きなフィールド番号を指定すれば特定列に対して一意処理をすることも可能です.

行番号を付ける(nl

$ awk '$0 = NR OFS $0'

$0 は入力のレコード全体を示しています.そこに,現在の入力に対する通し番号(NR)とスペース(OFS)と行全体($0)を代入し,再構築しています.あとは省略されたときの暗黙のアクションである '{print $0}' が実行されます.

標準出力にそのまま出力する(cat -

$ awk '1'

AWK での真偽値をうまく扱った例です.パターンに 1 がくると真になり,暗黙のアクションである '{print $0}' が実行されます.つまりそのまま出力されます.1 とは言わず,2 でも 3 でも 真です.もっと言えば,awk '!0' でいいです.AWK では 0 と空白以外は全て真だからです.

正規表現にマッチした行を表示する(grep

$ awk '/hogehoge/'

// で囲われたパターンを指定すると正規表現になります.これは,hogehoge にマッチする行を抜き出します.

正規表現にマッチしない行を表示する(grep -v

$ awk '! /hogehoge/'

// の否定は ! です.

コメント行を削除する

$ awk '! /^#/'

この例では,ハッシュシンボル(#)から始まるコメント行を削除しています.ちょっとかっこよく書いて awk {sub(/#.*/, "", $0)}1 としてもいいでしょう.

C 言語のように複数行にまたがってコメントがある場合は以下のようにすれば OK です.

$ cat file
asdfasd
asdfasd
asdfasd
asdfasd

a
v
/* コメント開始
c
d
s
*/
asdf 
asdf
$ cat file | awk '/\/\*/, /\*\//{next}{print}'
asdfasd
asdfasd
asdfasd
asdfasd

a
v
asdf 
asdf

/**/ に囲まれた行が削除されました.

指定行から指定行までを表示する

$ awk 'NR==10,NR==20'

通し番号 NR が 10 から 20 までを表示します.アクションは暗黙のアクションである '{print $0}' です.この範囲指定演算子は,カンマの前後がスイッチのように働きます.つまり,上記の場合には「組込変数 NR が 10 から組込変数 NR が 20 まで」という意味ではなく,「組込変数 NR が 10 になったらパターンを真にして,組込変数 NR が 20 になったらパターンを偽にする」という意味になるので注意しましょう [ref]

偶数行を表示する

$ awk 'NR%2==0'

% は余りを計算します.2 で割った余りが 0,つまり偶数のとき暗黙のアクションである '{print $0}' が実行されます.

奇数行を表示する

$ awk 'NR%2'

前項の逆です.2 で割った余りが 1 のとき表示します.NR%2==1 としたほうがわかりやすくなりますが,AWK では 1 は真ですので,省略しても OK です.

特定のフィールドのみを抜き出す

$ cat file
りんご 100円 192個
ばなな 170円 210個
爽健美茶 150円
グラタン ソース マカロニ チーズ じゃがいも
$ cat file | awk 'NF>=2 && NF<=3'
りんご 100円 192個
ばなな 170円 210個
爽健美茶 150円

NF はフィールド数を意味する組み込み変数です.NF-F オプションや組み込み変数 OFS に依存しますがデフォルトではスペースです.2 以上,3 以下のフィールド数を持つレコードのみの表示なので,「りんご」「ばなな」「爽健美茶」のみがとれています.

何も指定していないので,暗黙のアクションである '{print $0}' が実行されています.'{print $2}' などとすると,値段の列だけが抜き出されます.

$ cat file | awk 'NF>=2 && NF<=3{print $2}'
100円
170円
150円

最後に

AWK と使うとオーク(多く)の処理をコマンドラインからササッと完了することができます.学習コストも低く,サクッと習得でき行指向なテキスト処理には持ってこいです.ここやらで AWK を学んでみてはいかがでしょうか.

また,AWK によるワンライナーはシェル芸に対して AWK 芸とも言われるようです.また,AWK を含めてシェル芸であるといった考えを持つ方も一定数以上いるようです.どこまで長い AWK ワンライナーになるかが,分かれ目にようにも感じます.

シェル芸に関して,つまらない記事なのにストックがやたら伸びた以下の記事も参照してみてください.