【Awk & Jq活用講座】文字列の後方参照を理解する


2021/03/31

今回はcsvデータのセル文字列の後方参照をAwkとJqの2つのパターンでどう実現するのかを考えてみるちょっとした技術記事です。


はじめに

当サイトではオフィス業務のComputer-Aidedなハイブリッドな方法を模索し、より効率的なExcel業務を実現したい多忙なオフィスワーカー向けの主にAwkとSedを使うシェル講座です。

シェルスクリプトはどこでもどんなOSでも基本的に使えて、しかも一度使い方を覚えると、Excelと組み合わせて最高に効率の良いオフィスワークツールが作れることでしょう。

合同会社タコスキングダム|蛸壺の技術ブログ


正規表現の後方参照

どの言語にも正規表現の機能の一部として、検索パターンで対象の文字列内でマッチするかどうかを調べた後に、そのマッチした部分文字列をキャプチャすることが可能です。キャプチャした部分文字列を後処理から参照する仕組みが後方参照と呼ばれています。

例えば顧客データベースとして管理しているCsvファイルの中に、以下のようなクレジットカード番号があるとします。

            
            1234-5678-8765-4321
        
クレジットカード番号は見ての通りで、4桁の数字が4セットで構成されています。

例えばウェブ決済などのサービスで良く目にするのが、セキュリティー強化のための本人確認に使用する下4桁の数字ですが、この例でいうと
4321の下4桁部分を効率良く抽出するため方法...みたいなことを以降で考えていきましょう。

Awkの場合

Awk(Gawk)で正規表現による後方参照を行うにはgensub関数を使う必要があります。

gensub関数については
前回の記事でも触れましたが、今回も再度gensub関数の解説から行います。

一般に、正規表現を
(パターン)で丸括ると、その文字パターンによってグループ化され、マッチした値をキャプチャして後処理で参照できるようになります(後方参照)。

Awkでこの後方参照を行うには、
gensubを利用します。man awkでマニュアルの説明を要約しますと、

            
            gensub(r, s, h [, t])
    正規表現rにマッチする対象文字列tを探します。
    もし、オプションhが'g'か'G'ならば、rでマッチした全ての箇所をsで置換します。
    また、オプションhが数字で指定される場合には、
    指定されている番号目にマッチした箇所をsで置き換えます。
    対象文字列tが未指定の場合には$0に保持されている文字列が代替されます。
    置き換え文字列sで置き換わる位置は、シークエンス\nで与えられ、
    この数字nは0から9までの一桁の数字となります。
    このシークエンス\nは丸括弧で包んでキャプチャしたマッチング箇所を示すことにも利用できます。
    また\0はマッチしたテキスト全てを返し、&文字で結合されています。
    sub関数やgsub関数とは違い、元の対象文字列を変化させずに、
    関数の返り値として結果を返すことに注意してください。
        
という感じの関数です。

ではサクッと具体例で確認してみましょう。

以下ではクレジットカード番号(2列目)から4桁情報を抜き出すための操作をgensubで行っています。

            
            $ awk -F"," 'BEGIN { OFS="," } {
    last4digits_ = gensub(/[0-9]{4}-[0-9]{4}-[0-9]{4}-([0-9]{4})/, "\\1", g, $2);
    print $1, last4digits_;
}' << EOF
グルヤマ マムオ,1111-2222-3333-4444,000
ヌルタニ ポウスケ,5555-5555-6666-6666,222
ポロクチ ニャルミ,3333-4444-5555-9999,777
EOF
#👇出力
グルヤマ マムオ,4444
ヌルタニ ポウスケ,6666
ポロクチ ニャルミ,9999
        
このスクリプトを解説すると、csvデータの2列目($2)が置換される対象文字列としてgensub関数に置換されるのですが、この文字列は正規表現中の(...)でキャプチャされた文字列で置き換えられます。今回はキャプチャは一箇所しかないので、第三引数で"\\1"とすると、キャプチャ文字で全体が置き換わるようにgensubから結果が返されます。

jqの場合

Jqでは処理した結果を評価式(ラムダ)的に繋いでいくように処理する独特なスクリプトで記述します。

なので文字列の後方参照というどストレートな機能は無いものの、
capture関数を利用すると後方参照のようなオブジェクト操作が可能になります。

使い方はマニュアルの通りで、

            
            用法:
    capture(パターン)かcapture(パターン; フラグ)

この関数はJSONオブジェクトからパターン検索で得られたキャプチャに名前を付けて、
そのマッチング結果を格納します。
キャプチャする際に指定した名前はキー値として利用されます。

例:
    jq 'capture("(?<a>[a-z]+)-(?<n>[0-9]+)")'
    ...>
    入力:"xyzzy-14"
    出力:{ "a": "xyzzy", "n": "14" }
        
ということでこのcapture関数を使って、先程Awkで行ったスクリプト相当のプログラムをJqでも行ってみます。

            
            $ jq -sR '[ split("\n")[] | select(length > 0) | split(",") ] |
    map({
        "名義": .[0],
        "下4桁": .[1] | capture("[0-9]{4}-[0-9]{4}-[0-9]{4}-(?<digits>[0-9]{4})") | .digits
    })
' << EOF
グルヤマ マムオ,1111-2222-3333-4444,000
ヌルタニ ポウスケ,5555-5555-6666-6666,222
ポロクチ ニャルミ,3333-4444-5555-9999,777
EOF
#👇出力
[
  {
    "名義": "グルヤマ マムオ",
    "下4桁": "4444"
  },
  {
    "名義": "ヌルタニ ポウスケ",
    "下4桁": "6666"
  },
  {
    "名義": "ポロクチ ニャルミ",
    "下4桁": "9999"
  }
]
        
captureを使うことでJqでも簡単に正規表現の後方参照が可能であることが分かります。

なお、csv形式をJsonの2次元配列形式にして返すお約束の部分`
split("\n")[] | select(length > 0) | split(",") ] |`の解説は[以前の記事の内容でご確認ください。


まとめ

今回はAwkとJqで正規表現を使って後方参照をどのように扱うかを見てきました。

結論としては、Awkではgensub関数、Jqではcapture関数をそれぞれ利用すると簡単に後方参照できるということを理解していただけたかと思います。

Csvファイルでデータを捌く場合には両方共とても重宝するので、また別の記事でサラッと今回のテクニックが出現することもあるかと思いますが、そのときにはこの記事の内容を思い出してみてください。

記事を書いた人

記事の担当:taconocat

ナンデモ系エンジニア

主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。