便利なコマンドawk

LinuxやMacなどは最初からある程度コマンドが用意されており、そのうちいくつかはテキストデータの処理を得意としている。catやtr、pasteコマンドのようにごく単純な機能を提供するものから、単一の機能とは言えgrepやsortコマンドのように高度で多機能なものもあるし、sedのようにある程度の汎用性を備えたものもある。

デフォルトで用意されていることの多いテキスト処理系のコマンドで、特に多機能で汎用プログラミング言語の性能を備えたものにawkコマンドがある。テキストデータを1行ごとに処理することを前提としたコマンドだが、汎用性が高く、テキストデータの加工から集計までこなすことができる。ちょっとしたユーティリティとしても企業システムで使用するプログラミング言語としても使えるコマンドだ。

Macにも、最初からこのawkコマンドが用意されている。

  • Macに用意されているawkコマンド

    Macに用意されているawkコマンド

また、Linuxディストリビューションにもawkコマンドが用意されている。

  • Linuxに用意されているawkコマンド

    Linuxに用意されているawkコマンド

上記スクリーンショットを見比べてもらうと、同じ「awk --version」というコマンドを実行しているものの、その出力がかなり違っていることに気がつくと思う。Macに用意されているawkコマンドと、Linuxディストリビューションに用意されているawkコマンドは別物だからだ。

Macのawkで動くように作られたawkのスクリプトやawkコマンド、awkコマンドのワンライナーなどはLinuxでもそのまま使えることが多い。逆に、Linuxで作られたawkのコマンドを使ったスクリプトやワンライナーはMacでは実行できないことが多い。今回はこの辺りについて説明しよう。

awkの実装系はいくつかある

awkが登場した当時、提供されている機能は現在のawkコマンドよりも少なかった。だが、awkコマンドは便利で使われるシーンが多かったため、機能追加や拡張が行われた。実装系もいくつか存在しており、現在でもよく使われるものとしてはgawk、nawk、mawkなどが存在している。

どのawkコマンドも規約として標準化されている部分の動作はほとんど同じだ。規約として策定されている部分の機能だけを使っているのであれば、どの実装系を使っていても動くには動く。問題は、標準規約に策定されていない拡張機能を使った場合だ。拡張機能はそれぞれの実装系でしか使えないので、ほかのOSに持っていくと動かなかったりする。この辺りの事情を知らないと、例えばLinuxで使えていたawkを使ったシェルスクリプトがMacでは使えない、といったことが起こる。

Linuxのawkコマンド

Linuxディストリビューションに最初からデプロイされているawkコマンドはGNU awkだ。gawkという名前でインストールされていることもある。Linuxディストリビューションでは大体gawkが使われていると思っていればよいだろう。

gawkは現在主に使われているawkコマンドの中では最も機能が豊富だ。もちろん、標準規約として策定されている部分の機能は抑えつつ、さらに多数の拡張機能を提供している。awk単体でテキストデータ処理以外の汎用プログラミング言語的な使い方もある程度はできるようになっている。

GNU awkは便利なawkの実装系なのだが、拡張機能に関してはほかの実装系と互換性がない。GNU awkを前提としてスクリプトを書いた場合などは、基本的にはGNU awkでしか実行できないと思っておいたほうがよいかもしれない。Macで動かしたいとか、処理を高速化したいといった場合には、標準規約で定められている機能だけを使って実装を行うのがお薦めだ。

Macのawkコマンド

macOSにデフォルトで搭載されているawkコマンドはnawkと呼ばれるawkの実装系だ。nawkは最初に公開されたオリジナルのawkコマンドを拡張したバージョンで、オリジナルと区別するために呼ばれるようになった(new awkというところからnawkというわけだ。開発者であるBrian Kernighan氏の名前からとってBWK awkと呼ばれたり、Brian Kernighan氏というオリジナルawkの開発者の一人が担当していることからOne True Awkとも呼ばれている)。

awkのプログラミング言語としての機能は開発者らによって執筆されて1988年に出版された「The AWK Programming Language」という書籍が実質的な業界標準という位置付けにある。nawkはこの書籍で説明されている機能をほぼそのまま実装したものだ。現在ではFreeBSDなどいくつかのOSがnawkをデフォルト採用しているほか、macOSもnawkを採用している。

nawkにも「The AWK Programming Language」には掲載されていない拡張機能が存在するが、すごく少ない。GNU awkと比べるとほとんどオリジナルのままだ。nawkで実行できるようにしておけば、ほかの実装系でもそのまま実行できると思っておいてよい印象がある。

高速版awkコマンド

awkの実装系はほかにもいくつか存在しているのだが、特にmawkを取り上げておく。mawkも機能としてはnawkと同じで、「The AWK Programming Language」に掲載されている機能をベースとしつつ、ちょっとだけ機能拡張が加えられたバージョンだ。

gawkやnawkとの最大の違いは実行速度にある。mawkはバイトコードへ変換してから処理を実行するため、実行速度が速い。nawkで実行できるものであれば、インタプリタをnawkからmawkへ入れ替えるだけで処理時間が短くなる。高速化の度合いは処理やデータによるが、数倍の高速化が見込めると思っておいてよいと思う。入れ替えるだけで高速化できるのでとてもお手軽だ。

gawkは機能が多くて便利だが、拡張機能を使うとmawkで使えなくなるといった点が痛い。高速化も狙いたいなら、gawkを使っていてもなるべく拡張機能は使わないで実装しておいたほうが、mawkへ入れ替えて行う高速化が簡単になる。

動作の違いを見ておこう

ちょっとしたシェルスクリプトを使って互換性に関する部分の動作を見てみよう。次のようなシェルスクリプトを用意する。

cattime.sh - そのときの時刻を表示してから1行ごとテキストファイルの中身を表示するスクリプト

#!/bin/sh

cat "$1"                                            |
awk '
{
    printf("%s - ", strftime("%Y/%m/%d %H:%M:%S", systime()))
    print
}
'

このスクリプトはcatコマンドのようにテキストファイルの中身を表示するものだが、行ごとに行頭に現在時刻を表示する。

次のようなデータファイルを用意する。

データファイル - 13TOKYO.CSV

% cat 13TOKYO.CSV
13101,"100  ","1000000","トウキョウト","チヨダク","イカニケイサイガナイバアイ","東京都","千代田区","以下に掲載がない場合",0,0,0,0,0,0
13101,"102  ","1020072","トウキョウト","チヨダク","イイダバシ","東京都","千代田区","飯田橋",0,0,1,0,0,0
13101,"102  ","1020082","トウキョウト","チヨダク","イチバンチョウ","東京都","千代田区","一番町",0,0,0,0,0,0
13101,"101  ","1010032","トウキョウト","チヨダク","イワモトチョウ","東京都","千代田区","岩本町",0,0,1,0,0,0
13101,"101  ","1010047","トウキョウト","チヨダク","ウチカンダ","東京都","千代田区","内神田",0,0,1,0,0,0
13101,"100  ","1000011","トウキョウト","チヨダク","ウチサイワイチョウ","東京都","千代田区","内幸町",0,0,1,0,0,0
13101,"100  ","1000004","トウキョウト","チヨダク","オオテマチ(ツギノビルヲノゾク)","東京都","千代田区","大手町(次のビルを除く)",0,0,1,0,0,0
% 

先ほどのスクリプトでこのデータを処理すると、次のようになる。

テキストデータを処理した結果 - Linux

% ./cattime.sh 13TOKYO.CSV
2021/12/29 09:56:07 - 13101,"100  ","1000000","トウキョウト","チヨダク","イカニケイサイガナイバアイ","東京都","千代田区","以下に掲載がない場合",0,0,0,0,0,0
2021/12/29 09:56:07 - 13101,"102  ","1020072","トウキョウト","チヨダク","イイダバシ","東京都","千代田区","飯田橋",0,0,1,0,0,0
2021/12/29 09:56:07 - 13101,"102  ","1020082","トウキョウト","チヨダク","イチバンチョウ","東京都","千代田区","一番町",0,0,0,0,0,0
2021/12/29 09:56:07 - 13101,"101  ","1010032","トウキョウト","チヨダク","イワモトチョウ","東京都","千代田区","岩本町",0,0,1,0,0,0
2021/12/29 09:56:07 - 13101,"101  ","1010047","トウキョウト","チヨダク","ウチカンダ","東京都","千代田区","内神田",0,0,1,0,0,0
2021/12/29 09:56:07 - 13101,"100  ","1000011","トウキョウト","チヨダク","ウチサイワイチョウ","東京都","千代田区","内幸町",0,0,1,0,0,0
2021/12/29 09:56:07 - 13101,"100  ","1000004","トウキョウト","チヨダク","オオテマチ(ツギノビルヲノゾク)","東京都","千代田 区","大手町(次のビルを除く)",0,0,1,0,0,0
% 

これと同じことをMacで実行すると次のようになる。

テキストデータを処理した結果 - macOS

% ./cattime.sh 13TOKYO.CSV
awk: calling undefined function strftime
 input record number 1, file
 source line number 3
% 

strftime()やsystime()はGNU awkの拡張機能だ。このため、Linuxでは動くものの、Macではそのままでは機能しない。このように、gawkとnawkには動作に違いがある。

awkコマンドは便利なコマンドだ。LinuxでもMacでも便利であることは同じだ。どの機能が拡張機能で、どの機能が共通で使える機能なのか把握しておくとか、使用するawkの実装系を意識しておくと、何かと役に立つ。awkの実装系については、今後もう少し詳しく説明していこうと思う。

参考