編注1:標準入力やファイルから行単位でテキストデータを読み込み、置換などの処理を行って出力するUnixコマンド。
文字列の抜き出し
データ処理の基本は「データを加工・集計してまとめる」こと、つまり「元のデータから必要な部分を抜き出して加工する」ことです。前回では、フィールド単位で抜き出す方法を説明しました。今回はフィールド単位ではなく、「○文字目から○文字を抜き出す」といった処理から説明します。
この「○文字目から○文字を抜き出す」処理を行うには、substr関数を使います。では、2文字目から後の文字列を取得してみましょう。
$ echo 'abcde' | awk '{print substr($0, 2)}' bcde
このようにsubstr関数は、最初の引数に対象文字列、2番目の引数に、2文字目からであれば、2を与えることで必要な文字列を抜き出すことができます。
もちろん、この短縮形は以下のようになり、シェル芸編注2で使うとキーの打数が少なく効果的です。
$ echo 'abcde' | awk '$0 = substr($0, 2)' bcde
編注2:「シェル芸」とは、UNIXシェル(主にbash)のワンライナーを駆使して文字列加工を自由自在に操ることです。また、そのような能力をもつ人をシェル芸人と呼びます。
substr関数には3番目の引数を与えることもできます。この3番目の引数を指定すると、指定した文字数分だけの文字列を取り出すことができます。では、2文字目から3文字を取得してみましょう。
$ echo 'abcde' | awk '{print substr($0, 2, 3)}' bcd
他の言語にもsubstr関数やそれに似た関数があります。ただ、多くの言語は0(ゼロ)から数え始める「ゼロオリジン」です。しかし、AWKは1から数え始めるため、人間の思考に近いイメージで引数を与えることができます。これはレコードやフィールドについても同じですし、AWKに関する全てのインデックスは1から開始されます。もっとも、逆に分かりにくいという方も多いようです。
よく使われる方法として、「対象文字列の中にある特定の文字列から任意の文字列を取り出す」または「ある文字列までの任意の文字列を取り出す」というものがあります。このような場合には、index関数を併せて用いると効果的です。
$ echo 'abcde' | awk '{print substr($0, index($0, "b"))}' bcde
この例では文字列"b"から最後までを抜き出すのに、substr関数とindex関数の両方を用いています。
index関数は最初の引数に対象となる文字列、2番目の引数に検索したい文字列を指定すると、検索したい文字列の先頭位置を返します。検索文字列が存在しない場合には0(ゼロ)を返します。
このsubstr関数とindex関数の組み合わせは、文字列の抜き出しの中でも特に多く用いられるテクニックなので、覚えておくと便利です。
文字列の検索
文字列や正規表現の検索はすでに説明しています。
echo 'abcde' | awk '$0 ~ /b.*/' abcde
この一行野郎編注3を見ると、与えられた入力行が正規表現にマッチしているというのは分かりますが、具体的にどの部分がマッチしているのかは、このままでは分かりません。そこでAWKにはmatchという少し特殊な関数が用意されています。
$ echo 'abcde' | awk 'match($0, /b.*/)' abcde
編注3:ワンライナーのこと。1行で必要な処理を書き切る。
つまり、match関数は最初の引数に対象文字列を指定して、2番目の引数には検索する正規表現を指定します。
これだけを見るとmatch関数はマッチ演算子"~"と同じような気がします。では、なぜmatch関数が特殊なのでしょうか。それはmatch関数が値を返すだけでなく、組込変数RSTARTとRLENGTHをセットするからです。この組込変数RSTARTにはマッチした最初の位置、組込変数RLENGTHにはマッチした長さが格納されます。
echo 'abcde' | awk 'match($0, /b.*/) {print RSTART, RLENGTH}' 2 4
つまり、正規表現"b.*"というのは、$0の2文字目から4文字が該当した部分であるということです。
さらに、match関数がセットした組込変数RSTARTとRLENGTHを使って、以下のような記述をすることもよくあります。
$ echo 'abcde' | awk 'match($0, /b.*/) {print substr($0, RSTART, RLENGTH)}' bcde
つまり、正規表現に該当する箇所だけを抜き出すのにmatch関数とsubstr関数を用いています。
AWKで扱う多くの場合にはマッチ演算子"~"だけで十分なことが多く、match関数を用いることは少ない気がします。なぜなら、match関数の最大の特徴である組込変数RSTARTとRLENGTHを使おうとすると、必然的に一行野郎で記述するには長過ぎるからです。
シェル芸勉強会で生まれた本来の目的ではない最大の成果の一つに"grep -o"というものがあります。grepコマンドの引数として"-o"を付けると正規表現にマッチした部分だけを抜き出す、つまり先ほどのAWKスクリプトと同じことがgrepコマンドでできてしまうのです。
$ echo 'abcde' | grep -o 'b.*' bcde
しかも、それだけでなく、シェル芸の中でもよく使われる、文字列を1文字ずつ分離し、それぞれに改行を挿入する「横→縦変換」も瞬時にこなしてくれるので便利です(逆に、縦→横変換は引数なしのxargsコマンドで行えます)。
$ echo 'abcde' | grep -o '.' a b c d e
少し脱線しましたが、このmatch関数は最新のGNU AWK(gawk)では拡張されて第3の引数を取れるようになっており、さらに便利になっているのですが、こうしたGNU拡張のお話は別の機会に譲りたいと思います。