awk 《第6回》正規表現を使ってみる

Stefan KellerによるPixabayからの画像

「正規表現」という便利な考え方があります。

歴史を紐解くと1950年代に「数学者のスティーヴン・クリーネは1950年代に正規集合と呼ばれる独自の数学的表記法を用い、これらの分野のモデルを記述した」とwikiに書かれていました。

それをケン・トンプソンと言う人がテキストファイル中のパターンにマッチさせる手段として、この表記法をUNIXに導入することで、早々にawkにも組み込まれたようです。

awkが作られたのが1977年とのことですから、50年前から「正規表現」に対応していたわけです。

通常使う正規表現

.:ピリオドは「一文字」という意味
.*:アスタリスクをつけると「0文字以上」
.+:プラスだと「1文字以上」になります
:キャレット(山なり)は、先頭という意味
必ずしも行頭というわけではなく、タブなりカンマなりでセパレートした分断単位の「先頭」としても指定できます
$:ダラーは行末ですがキャレット同様に分断単位の「末尾」として指定できます
¥n:改行
¥t:タブ
[0-9]:鉤括弧([])も正規表現としての特殊文字になります
ハイフンは「範囲」を意味し「0から9」になるので[0-9]+とすれば数字1文字以上を意味します
同様にアルファベットなら[a-z|A-Z]+とすることでアルファベットの出現を指定できます
:縦棒は「または」になります

通常使う正規表現は、このくらいです。あまり駆使しすぎると「可読性」が落ちるので、ほどほどに使うのがスピーディーでいい「加減」なのではないかと思っています。

凝ったことがしたければ「sed」のほうがオタッキーな正規表現が使えると思います。

/・・・/スラッシュで囲むと、その中に書かれていることは正規表現になります。

上記の例では行頭から改行間に1文字もない行は「print」しないで次(next)を読み込みに行くようにしてあります。「¥n」でなく「$」でも同じ結果になります。

この正規表現は、うまく使うと「なんて賢いのだろう」と感心することが多々あります。正規表現が賢いのも賢いのですが、それを考える人がいることに驚きを隠せません。

ちなみに、マイクロソフトは、そうした考えを積極的には取り入れていません。確かに、起源がUNIXであったこともあって、マイクロソフトとしてはエンドユーザーを考えると正規表現を積極的に取り入れることには躊躇があっただろうことは想像できます。

if文で正規表現を使ったマッチングをするのには、

上記では「$0」、つまり行データとのマッチングをしていますが、セパレートしている場合は指定ポジションとのマッチングに「チルダ(~)」を使ってパターンマッチングをします。上記は「0」が一個以上あるパターンが行の中にあれば出力します。

awkの約束語

FS:セパレータを定義します。定義しないと半角スペースがデフォルトです
OFS:出力するときのセパレータを定義します
FILENAME:処理をしているターゲットファイルのファイル名がこの変数にセットされます
NR:レコード数(ようはテキストファイルの改行をカウントします)
NF:フィールド数

特にExcelからCSVにすると、セル内改行されていればフィールド数が狂うことがあります。それに気づかずに処理を進めてしまうと後で手戻りになるので、怪しい場合は「NF」でフィールド数のチェックが不可欠になることがあります。

FS」は常套句として使います。と言うのは、CSVはいろいろ不都合があるのでタブ区切りにして加工することが多いです。「不都合」というのは、セル内で半角カンマが使われていると、それでフィールド数が狂います。

出力ファイルのセパレーションをタブにするなら「OFS」に必要になります。

NR」を使うときは、だいたい、1行目の項目名を読み飛ばすときに使います。ただし、「*.txt」のような指定をした場合はファイルが切り替わっても加算されしまいますので「++cc」のようなカウンターをつけてファイル名が変わるところで初期化する必要があります。

FILENAME」をawkシリーズ《第5回》でやったように「split」をかけるなどして標準出力に書き出すような場面で使用します。

NULLと空文字の判定

awkにおける「NULL」と「空文字」の判定を説明します。

このデータに対して

というスクリプトで処理をすると

となります。つまり「0」も「00」も「空文字」も「NULL」と判定されています。

よって「空文字」判定には「NULL」を使うよりは「“”」を使うほうが賢明と言えそうです。

VBAでは結構面倒な解釈がされていてたくさんの関数が用意されていますが、どれだけ必要があるのかは不明です。その昔のC言語では「(void *)0」などと言われて、意味が全く分からなかったです。