Shoeisha Technology Media

CodeZine(コードジン)

特集ページ一覧

シェルコマンドを使った処理の効率化は
AWKの行(レコード)操作がカギをにぎる

► 「シェル芸」に効く AWK処方箋 第2回 (月刊『USP MAGAZINE 2014 May (Vol.13)』より転載)

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2014/05/26 14:00

 行単位で処理を行うことは非常に重要なことです。なぜでしょうか。それは他のUnix系ツールも行単位で処理を行うからです。シェル芸編注1ではパイプを使って次のコマンドに出力を渡しますが、次のコマンドも行単位で処理を行うことが多いため、適切な行をAWKで抜き出すことで円滑な並列処理が可能になります。そのため、シェル芸の効率化は行の処理の効率化と言い換えることができます。そこで、今回はAWKを用いた行の処理について説明していきます。

目次
編注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という数が表示されます。

本連載が単行本になりました!

「シェル芸」に効く!AWK処方箋

Amazon  その他

「シェル芸」に効く!AWK処方箋

著者:斉藤博文
発売日:2017年1月31日(火)
価格(POD):2,160円(税込)
価格(電子書籍):1,728円(税込)

本書について

 コマンドであり軽量言語(LL)の元祖でもあって、シェルでのテキストデータ処理には便利で手放せない「AWK」の魅力と書き方、シェルコマンドと組み合わせたテクニック(シェル芸)を解説。
 プリントオンデマンド(POD)と電子書籍にて、絶賛発売中です!


  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

  • 斉藤 博文(サイトウヒロフミ)

    最初にAWKと出会ってから○十年、AWKの魅力に取りつかれ、勢い余って「日本 GNU AWKユーザー会」を立ち上げています。会としてOSCなどのイベントにも出展しつつ、GNU AWKの開発も手伝っています。「USP友の会」では幹事役ですが、「シェル芸勉強会」にはほぼ毎回参加して一緒に勉強しています。...

バックナンバー

連載:USP MAGAZINEコラボレーション連載/「シェル芸」に効く AWK処方箋
All contents copyright © 2005-2019 Shoeisha Co., Ltd. All rights reserved. ver.1.5