編注1:「シェル芸」とは、UNIXシェル(主にbash)のワンライナーを駆使して文字列加工を自由自在に操ることです。また、そのような能力をもつ人をシェル芸人と呼びます。
「行」=「レコード」
デフォルトでAWKは「行」を「レコード」というものとして扱います。正しくは組込変数RS (Record Separater) で区切られたものをレコードと呼びますが、デフォルトでは、この組込変数RSは改行になるため、行がレコードそのものになります。また、このレコード単位の分割は、アクションでテキストファイルを読み込むと必ず行われるものとなっているため、ユーザーが特別に分割処理をしなくてもレコード単位で扱ってくれる、とても便利な仕組みです。
特殊なものとして、段落単位で読み込むために組込変数RSに空文字列を代入するものや、GNU AWK編注2ではファイル全体をひとつのレコードとして読み込む手法、組込変数RSに正規表現を使う手法などもあるのですが、シェル芸の中ではあまり使われないため、ここでは扱いません。ただ、組込変数RSに正規表現を使うことで新たな可能性が広がります。興味がある人はGNU AWKを使ってみましょう。
編注2:GNUプロジェクトで開発されているAWK。オリジナルのAWKにない機能が追加されている。
直接行を指定する
よく使われるものに指定行だけを抜き出すという処理があります。たとえば、先頭1行目だけを抜き出すような場合です。AWKのアクションではレコード単位に分割していき、現在処理を行っている行番号が組込変数NR(Number of Record)に格納されます。つまり、この組込変数NRを使って真偽判定を行えば、指定した行を抜き出せることになります。
前述の1行目だけを抜き出す場合には、次のようになります。
$ seq 1 10 | awk 'NR == 1 {print $0}'
前回勉強した{print $0}が省略できることを使うと、これは次のように短く記述することができます。
$ seq 1 10 | awk 'NR == 1'
このように真偽をパターンを使うだけで簡単に判別し、行の処理を行うことができます。
先頭10行を抜き出す
headコマンドはデフォルトで先頭10行を出力するものです。これをAWKで作ると次のようになります。
$ seq 1 100 | awk 'NR <= 10'
上記の例では、組込変数NRが10以下の場合というパターンで真偽判定を行っています。スペースを除けば6バイトという短さで、非常に簡単にheadコマンドと同じものができてしまいました。
さて、本当にheadコマンドと同じなのでしょうか。実は大きく違います。headコマンドは指定行まで表示し終わると、パイプの前のコマンドに対してKILLPIPEを出しますので、前のコマンドはエラーを出して止まります。
しかし、上記のAWKスクリプトの場合には、パイプの前のプロセスは最後まで実行されることになります。このため、bashの配列$PIPESTATUS[@]や、zshの配列$pipestatus[@]でエラー処理を行っているような場合には、こうした挙動の違いに注意が必要になります。エラーを出さないAWKの方が良いと思われるかもしれませんが、パイプの前のコマンドが大きなファイルを扱っている場合には、そのプロセスが終わらないため、余計にCPUパワーを消費してしまうことになります。
末尾10行を抜き出す
headコマンドがあればtailコマンドもAWKで記述できるのですが、AWKは必ず先頭の1行目からしか読むことができないため、tailコマンドをAWKで作成するのは効率的ではありません。普通に組むと次のようになります。
$ seq 1 100 | \ awk '{a[NR]=$0} END {for (i=NR-10; i<=NR; i++) print a[i]}'
ただ、上記のスクリプトだと行数分だけ配列を生成するため、大きなファイルを扱う場合にはメモリを大量に消費してしまいます。そこで、よく使われる手法としてリングバッファを使う方法があります。
$ seq 1 100 | \ awk '{a[NR%10]=$0} END {for (i=NR+1; i<=NR+10; i++) print a[i%10]}'
上記のようにすることで生成される配列の要素数を10個に制限し、メモリを節約することができます。
とはいえ、tailコマンドをAWKでまねるのは非効率です。どうしてもAWKでtailコマンドのようなものを作りたい場合には、以下のようなシェル芸で逃げましょう。
$ seq 1 100 | tac | awk 'NR <= 10' | tac
tacコマンドとはそのスペルがcatコマンドの逆になっているとおり、逆順に表示する便利なコマンドです。シェル芸でも時々使われる便利なコマンドです。
範囲を指定する
AWKのパターンには論理演算子を使うことができます。「AとBの両者が真の場合にパターン全体が真になる」ことを示す"A && B"と、「AまたはBのどちらかが真の場合にパターン全体が真になる」ことを示す"A || B"です。これらを使うと、10行目から20行目までという範囲を指定して出力することができます。
下記は「組込変数NRが10以上かつ組込変数NRが20以下」という意味になり、10行目から20行目までを表示します。
$ seq 1 100 | awk 'NR >= 10 && NR <= 20'
範囲指定演算子
AWKには、sedコマンドのように範囲を指定する演算子","(カンマ)があり、10行目から20行目を出力する際には、次のように書けます。
$ seq 1 100 | awk 'NR == 10, NR == 20'
この範囲指定演算子は、カンマの前後がスイッチのように働きます。つまり、上記の場合には「組込変数NRが10から組込変数NRが20まで」という意味ではなく、「組込変数NRが10になったらパターンを真にして、組込変数NRが20になったらパターンを偽にする」という意味になるので注意しましょう。
これを説明するために少し違う例にしましょう。
$ seq 1 10 | awk 'NR % 2 == 0, NR % 3 == 0'
どういう答えになるか分かりますか。%は余りを計算する演算子です。したがって、「組込変数NRを2で割った余りが0になったらパターンが真になり、組込変数NRを3で割った余りが0になったらパターンが偽になる」ということを繰り返します。そのため、2、3、4、5、6、8、9、10という数が表示されます。