代入演算子
代入演算子にも真偽があります。一部のAWKの実装で加わった特殊な変数を除きAWKの代入は破壊的に代入できますので、代入が成功しているから必ず真を返すという誤解をしていませんか? AWKでは代入の可否と戻り値は関係ありません。
では、以下のようなもので第2フィールド編注2を"B"にしてみます。
$ echo "a b c" | awk '{$2 = "B"; print $0}' a B c
問題ないですね。代入が常に真であるなら、以下のようにパターンに記述することができます。
$ echo "a b c" | awk '$2 = "B"' a B c
これだけを見ると、代入の可否が真偽を表しているように思われるかもしれません。では、第2フィールドに空文字列を代入して第2フィールドを削除したいとします。
$ echo "a b c" | awk '{$2 = ""; print $0}' a c
同様に以下のようにして第2フィールドを削除してみます。
$ echo "a b c" | awk '$2 = ""'
何も出ませんね。エラーもないことから、代入そのものは成功しているのでしょう。このパターンに記述した代入がうまく動作しないのは代入の戻り値が代入の真偽ではないからです。
そこで、以下のようにprint文で代入の戻り値を確かめてみましょう。$ echo "a b c" | awk '{print $2 = "B"}' B $ echo "a b c" | awk '{print $2 = ""}'
お分かりになりましたか。代入の戻り値は左辺値だったのです。先ほどの第2フィールドを削除するものをパターンだけで記述したい場合には、例えば以下のようにします。
$ echo "a b c" | awk '!($2 = "")' a c
つまり、代入には成功しているものの、代入演算子が空文字列を返してしまい、レコードが表示されないということなので、戻り値を"!"で反転してやれば良いわけです。否定演算子"!"は真偽を反転する演算子です。
また、よく使われる手法として$1だけを表示するには以下のように$0に代入します。
$ echo "a b c" | awk '$0 = $1' a
この真偽は、$0に数字の0または空文字列が代入されない限り、真になります。
編注2:AWKでは何もしなくても、行からある文字(デフォルトでは半角空白とタブ文字)で区切られた「フィールド」を取り出せます。変数$1、$2……がそれぞれ第1フィールド、第2フィールド……を参照するルールになっており、"a b c"という行なら、print $2ではbが表示されます。$0は行全体です。
変数
AWKにはいくつかの組込変数があります。代表的なものには現在のレコード数を示すNR(Number of Record)と現在のレコードの中のフィールド数を示すNF(Number of Field)があります。これらはパターンの中でよく用いられます。例えば、フィールド数が3のものを表示したい場合には以下のようにします。
$ echo "a b c" | awk 'NF == 3' a b c
前述の比較演算子により真偽判定が行われています。一方、間違って以下のようにするとどうなるでしょうか。
$ echo "a b c" | awk 'NF = 3' a b c
この場合には代入演算子の真偽が左辺値なので、NFに数字3が代入されて、パターンの値は数字の3になり、結果としてパターンは真になってしまいます。つまり、結果として同じでも真偽の対象が異なってしまうのです。
このNFを用いた記法には癖があります。GNU AWKやmawkで第1フィールドから第2フィールドまでを出力したい場合には以下のようにできます。
$ echo "a b c" | awk 'NF = 2' a b
NFに数字の2が入ってしまいますので、AWKはフィールド数が2であると解釈して$1から$2までを表示しますが、AWKの実装に依存しますので、この記法には注意してください。
さて、NFの真偽を使った便利な使い方があります。
$ echo "\n a \n b \n \n" | awk 'NF' a b
分かりましたか。NFにはフィールド数が格納されますが、スペースやタブだけの行、空の行のようにフィールドがない場合には値が数字の0になるためパターンは偽となり、結果としてスペースやタブだけの行や空の行を削除できます。「シェル芸」勉強会でも何度か登場していますが、意外に便利です。
関数
関数とは何でしょうか。中学や高校で習う関数と同じように、AWKの関数は一価関数と言われ、戻り値は1つです。この関数の戻り値を使った真偽判定を行うこともできます。
length関数は文字数(バイト数ではない)を返す関数ですが、引数がないと$0を用い、しかもその場合には丸括弧も不要という、ちょっと変わった関数です注4。
$ echo "\n a \n b \n \n" | awk 'length' a b ←スペースだけの行が出力されている
先ほどのNFを用いた場合にはスペースやタブだけの行は削除されましたが、レコードに今度は何らかの文字があれば表示されるようになります。
関数は戻り値があるので分かりやすいのですが、よく間違えるものにsub()関数とgsub()関数があります。sub()関数は置換に成功すれば数字の1を返して失敗すれば数字の0を返し、gsub()関数は置換に成功した個数を返します注5。
$ echo "a b c" | awk '{print gsub(/a/, "")}' 1
したがって、マッチする箇所を数えるには以下のようにすれば良いわけです。
$ echo "ab ba ab" | awk '$0 = gsub(/ab/, "")' 2
マッチしない場合には数字の0をgsub()関数は返すのですが、数字の0は偽であるため、何も表示されず0と表示されません。でも、0と表示させたいですね。この場合には以下のようにします。
$ echo "ab ba ab" | awk '$0 = gsub(/ac/, "") ""' 0
gsub()関数は数字の0を返しているのですが、数字の0は偽になります。ところが空文字列を連接してあげることで文字列の0の扱いになります。文字列の0は偽にはならないのです。
フィールドをそれぞれ配列にしたいような場合には以下のようにします。
$ echo "ab ba ab" | \ awk 'split($0, arr) {print arr[1]}' ab
split()関数は分割個数が戻り値になりますので、このような使い方ができるわけです。
注4:この引数も括弧もないlength関数の取り扱いは議論されていて、将来的に廃止になるかもしれません。
注5:置換後の文字列を返すには、GNU AWKであればgensub()関数が用意されていて、後方参照による置換も可能な高機能関数となっています。
まとめ
AWKの真偽を使った例をいくつか挙げながら、真偽について深く掘り下げてみましたが、いかがでしたか。やっぱりAWKは呪文だと思われた方、AWKはパターンとアクションの言語であり、そのパターンは真偽値で判断されるということを知っているだけで様々なことができると思われた方などがいらっしゃると思いますが、今回例として挙げたAWKスクリプトは長くても20文字程度ですので、頭の中で良く考えてみてください。
最後に、「シェル芸」勉強会に筆者はできるだけ参加しています。勉強会は講習会でも授業でもありませんので、積極的に質問をしてみてください。質問をするということは、単に自分が答えを導き出すためのヒントや方向性を得るだけでなく、勉強会の参加者全員に考える機会を与えてくれます。これは「シェル芸」勉強会に限らない話ですので、IT関連の勉強会では積極的に質問してみましょう。
『USP MAGAZINE』は日本で唯一のシェルスクリプト総合誌(毎月25日発売)。最新号の2014 May(Vol.13)では、新連載「アジャイル改善塾」がスタート! 著者の山海一剛さんが実体験をもとに、自身がソフトウェアアーキテクトを務めるユーザ系SI会社へアジャイル開発を導入し、組織文化を変えようと取り組む物語。涙なしには読めません!
そのほか、エンジニアが集うトークイベント「TechLION」のレポートとして、「学内のある棟をTCP/IPで何でも管理可能にした」という江藤浩氏(東京大学大学院情報理工学系研究科教授、WIDEプロジェクト代表)の強烈なトークを収録。もちろん、本連載「AWK処方箋」の最新回もあります!
► お求めは、Amazon.co.jp、USP研究所のWebサイト、取り扱い書店まで。
USP MAGAZINE 2014 May(Vol.13)
価格:500円+税
仕様:B5版 62ページ オールカラー
発行人:ユニバーサル・シェル・プログラミング研究所