inserted by FC2 system Home / AWK

Windows 環境なら busybox-w32 ってのが有って、それで awk が使える。
425,472 busybox.exe (BusyBox v1.25.0-FR)
ちょっと試した限りは問題無さそう。
 と思ったら Windows 10 では問題有り。64bit 版 Windows が駄目なのかも…
でもこれで 400KB だから大した物だ。

古くから常用している Windows版 : 1998/08/22 22:02 193,536 mawk32.exe
SJIS で日本語対応。

参考

←お世話になった本、と言うか今でもたまに読み直す。とても好きな本。

概要

正規表現:置換

(2017-10-21) 探すだけでなく同時に置換したい場合がある。

sed を始め、各エディタでは()でグループ化した文字を$や\で指定できるようだ。
awk には sub/gsub 関数が用意されているのだけれども、その方法は使えないようだ。

拠って match && 置換の2ステップを要する。
例えば(良い例ではないが)小数点4桁以上の数値表現部分を小数点3桁にカットするには

if (match(s, /\.[0-9][0-9][0-9][0-9]+$/))
    print substr(s, 1, RSTART+3)

但し gawk なら gensub が使えるので、次のように書ける(残したい部分を括弧で括っている処がミソ)。

print gensub(/(\.[0-9][0-9][0-9])[0-9]+$/, 
             "\\1", "g", s)

説明を良く読まないと中々判らないが、sed 風に \n で取り出す訳。

match と substr の組み合わせで十分な気もするが、複雑さが増すとバギーなコードになりそうだ。

例えば、次のように整数部と小数部を持つ場合にだけ、変換するよう仕様を変えたとする。

yahoo.co.jp.4649.123456 --> yahoo.co.jp.4649.123
yahoo.co.jp.123456 -------> そのまま

regexp に整数部の適合を加え、次のように書いてみる。

if (match(s, /[0-9]+\.[0-9][0-9][0-9][0-9]+$/)) {
    tmp = substr(s, RSTART, RLENGTH)
    print substr(s, 1, RSTART + index(tmp, ".") + 2)
} else 
    print s

マッチした後に index でドットを探すが、残念ながら開始位置(及び長さ)を指定出来ないので tmp へ一旦切り出している。さもないとマッチ以外の部分も含んでしまう。

一方、gensub なら必要な部分を括り(グループ化)、\1等で参照すれば良い。改めて検索し直さなくて済む部分が大きい。

print gensub(/([0-9]+\.[0-9][0-9][0-9])[0-9]+$/, 
             "\\1", "g", s)

尤も、gensub が万能と言えるのかは疑問で、状況に応じて使い分けるのがベストと思われる。

例えば、第2引数は置換文字列だから連接は出来ても、数字に見立てた演算は出来ない。マッチした数字文字列に+10しようと思って "\\1+10" なんて書いても駄目)。
「そりゃ置換じゃない!」と言われればそれまでだが、awk だとつい出来そうに思えてしまう…

そう言う場合はやっぱり、match+substr 手順を踏まないといけない。

if (match(s, /[0-9]+\.[0-9][0-9][0-9][0-9]+$/)) {
    tmp = substr(s, RSTART, RLENGTH) + 10
    print substr(s, 1, RSTART-1) sprintf("%.3f", tmp)
} else
    print s

正規表現:ミニC版

http://usskim.blog37.fc2.com/blog-entry-571.html

実装っぽい処が判ると、例えば^を付けても付けなくても良い場合、出来るだけ付けようという気になる。

標準エラー出力

Windows 環境でも /dev/* が有効なようだ。

print "Serious error detected!" > "/dev/stderr"

頻繁に書くのなら変数にセットしておくと見栄え良いかも。

BEGIN {
    STDERR = "/dev/stderr"
    print "Hoge" > STDERR
}

複数データファイル

単に引数に並べれば良い。が、ファイルの区切りを知るにはちょっと工夫が要る。
いきなり getline を持ち出すのは後にしておいて、awk に現れる変化を探すと…

ここで、これらを判断するタイミングであるが、入力処理しているブロックでやろうとすると、パターンには条件が付き物であるから定型に成り難い。
条件無しのブロックで考えた方が良さそう。

{ if (FNR == 1) ... } # ここなら新しいファイルを始めた事が判る。

/パターン/  { ... } # ここがファイル毎のやりたい処理。

最後のファイルを処理した後は END に書く。

「パターンによるレコード範囲の指定」の代案

パターンにカンマを使うとレコード範囲を意味するそうな。
中々良いアイデアに思うが、各レコードの内容が似てないとそのまま使うには難しい。

例えば dir /s や ls -R のような、ヘッダがディレクトリを表し、それにファイルリストのレコードが続き、それらが繰り返されている様な構造だと、
パターン一致式を書くのが中々難しい。 (正規表現に慣れた人なら苦も無かろうが…)

その場合は教科書にも有るようにフラグ使う方が楽。
例えば ls -lR で得たリストからhogeディレクトリ(と、その中のファイルのレコード)を無視するには

$1 ~ /^\//  {
    if ($1 ~ /^\/hoge/) { skip = 1; next }
    skip = 0
}
!skip { ... }

skip フラグ変更はヘッダ(ディレクトリ)を表すレコードの場合だけと言う物。
実の処は、~を含まない文字列を表す正規表現が書けなかったのが一番の理由なのだが orz
シンプルなのでコメント不要な位だし、凝った正規表現より明らかに遅くも無いだろう。

ふむ、でも次の様に書けばさら良さそう。

$1 ~ /^\/hoge/  { skip = 1; next }   # next により以降のパターンも評価しない。
$1 ~ /^\//      { skip = 0 }
!skip { ... }

余談:
IT屋と言うかデータ処理屋さんは、フラグを「悪」と見做している気がしてならない。
ダサイと思っているのか、あるいは苦手なのか解らないが、例え難解でも正規表現等を好むようだ(awkの先生方は別格)。
難解ならコメントを添えるべきだが、これまた不思議な事に、そういう箇所には決してコメントが書かれていない orz

数値

文字列

配列

16進表記文字列を数値化

拡張版awk?には用意されているらしいが、手持ちのawkには無かった。
探すと連想配列を使った旨い方法を見かけたが、折角自力で考えたので披露しておく。
ダサい方法だがチェックも兼ねているので他への応用として残しておく。

function strhex2dec(str,    y,dig,s,tmp)
{
    y = 0
    dig = 1

    for (len = length(str); len >= 1; --len) {
        s = substr(str, len, 1)
        if      (s ~ /[0-9]/)           tmp = s + 0;
        else if (s == "a" || s == "A")  tmp = 10
        else if (s == "b" || s == "B")  tmp = 11
        else if (s == "c" || s == "C")  tmp = 12
        else if (s == "d" || s == "D")  tmp = 13
        else if (s == "e" || s == "E")  tmp = 14
        else if (s == "f" || s == "F")  tmp = 15
        else
            break;

        y += tmp * dig
        dig *= 16
    }

    return y
}

ダサいと書いたが連想配列を使う場合、x in array で存在確認する必要が有る。そうしないと(自動的に)連想配列の要素がセットされる、と書いてある。上の例は満更でもない。

(2017-5-18) 某の場合、awk で 16進表現文字列を扱う場合は何らかのツールの出力を読む場合が多い。ならば 2/4桁を前提にすっきり書いた方が良さ気である。

function x4(s)
{
    if      (s ~ /^[0-9]/)  return s + 0
    else if (s ~ /^[aA]/)   return 10
    else if (s ~ /^[bB]/)   return 11
    else if (s ~ /^[cC]/)   return 12
    else if (s ~ /^[dD]/)   return 13
    else if (s ~ /^[eE]/)   return 14
    else if (s ~ /^[fF]/)   return 15
    return 0
}
function x8(str)
{
    return x4(substr(str, 1, 1)) * 16 + 
           x4(substr(str, 2, 1))
}
function x16(str)
{
    return x4(substr(str, 1, 1)) * 4096 +     # 16 * 16 * 16
           x4(substr(str, 2, 1)) * 256 +      # 16 * 16
           x4(substr(str, 3, 1)) * 16 + 
           x4(substr(str, 4, 1))
}

{ print x16($1) } と書いてテスト。桁数が合わないと正しくなくなるが、良かろう。

>echo fffe | busybox awk -f xxx.awk
65534

ビット表示

printf 書式には無い。C にも無かったはず。珠に出力したいので作っておく。まずはニブルに対応してバイトに。

function bit4(v)
{
    return int(v/8) % 2 "" \
           int(v/4) % 2 "" \
           int(v/2) % 2 "" \
                  v % 2
}
function bit8(v)
{
    return bit4(int(v/16))  \
           " "              \
           bit4(int(v%16))
}

これで { print bit8(x8($1)) } とでも書いて
>echo a5 | busybox awk -f xxx.awk
1010 0101
を得る。ok!

ここまで出来ればビット演算出来そうだが、気乗りしない。

バイナリファイルを処理する

awk そのものでバイナリファイルを読みのは難しい。が、データをテキストへ変換してやれば良い訳で、そこそこのファイルサイズなら hexdump や od でテキスト変換しまう手がある。
尤も、バイナリファイルのみならず、通信データをコンソールで垂れ流しながら、後で解析したい、なんて場面も想定。

ひとまず今回は(手元に有る)バイナリファイルをゆっくり料理すれば良い状況なので、好きなツールでテキストへダンプする。

16進表記の文字列だと(例に示したように)効率が悪いので、今回は暗黙で数値と見做してくれる10進表記にする。
busybox od -tuC binfile
とやればバイト値が10進表記で並ぶ、addr+16列/行のテキストが得られる。
この場合は $1 が addr で、$2-$17 がデータ。

10進文字を旨く読み取れているかテスト。

{
    if ($0 == "*") {	# 0が連続する処
        print $0
        next
    }
    printf("%s ", $1)
    for (i = 2; i <= NF; ++i)
        printf("%02x ", $(i))	# 16進で表示

    print("")
}

od で10進出力したものを食わせてみる。
> busybox od -tuC binfile | busybox awk -f xx.awk

od -txC で出力したものと同じ結果を得る。ok!
busybox だけで済むから便利なもんです。


ここからは都度…なんだけど、毎度考えるには面倒なので、流用出来そうな基本形を考えておく。

まず入力テキストとなる od の書式を決める。
addr.も使えるように、省略無しにして 10進とする。黙ってると * で省略されますんで都合が悪い。
これで最終レコード以外は 17列のレコードとなる。

> busybox od -v -Ad -tuC binfile
0000000  77  90 144   0   3   0   0   0   4   0   0   0 255 255   0   0
0000016 184   0   0   0   0   0   0   0  64   0   0   0   0   0   0   0
0000032   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
…

シーケンシャルな(バイトストリーム風)読み取り関数を拵える。

function fetch()
{
    if (POS < 2 || POS > NF) {
        if (getline <= 0)
            return ""
        POS = 2
    }
    return $(POS++)
}

POS はグローバル変数で一行16列のバイトデータの位置を示し、16列を超えて読み取ろうとした処で次の行を読み取ろうという作戦。
終端到達したら空を返し、その検査を毎度要するのでスマートでないが、今はこれで orz

バイト単位で fread するようなものなので、通常のパターンアクションではなく、BEGIN ブロックで全て済ませてしまう。

BEGIN {
    POS = 0
    …
    exit
}

awk の標準は「行単位でパターンアクションを呼び出す」コールバック風な流れなんだが、今回は逆を行く。

 

2017-8-27 Win10 駄目
2017-5-14 バイナリファイルについて
2017-2-19 strtok 関数について再考
2016-8-17 修正、追記。
2015-9-1 修正、追記。
2014-6-30 追記。整理。脱線。
2011-5-28 ちょっと手直し。


Home / AWK

© 2008 usskim    http://usskim.web.fc2.com/
inserted by FC2 system