やっと来たAWK

 

AWKとは:

入力を行単位で一行づつ順番に処理していくスクリプト言語。

ファイルの11行をレコードと見なして、それを入力として処理を進めるようなことが得意な言語である。

加えて、レコード内の文字列をある区切り文字によって区切られたフィールドとして扱うこともできる。

また、文字列操作において正規表現が非常に容易に使えるところも見落とすことの出来ないAWKの強力な利点である。

Awkには旧来の実装であるawkの他に、新しい実装であるnawkgawkがある。環境によってどれが使えるかまちまちのようだ。

 

最初のawkスクリプト:

ls -alで表示されるファイルのバイト数を合計するには、

例)

$ ls -al | awk ‘{a+=$5} END {print a}’

 

でよい。もしくは、awkのスクリプト部分を、

 

#!/usr/bin/awk -f

# sum.awk

{

              a += $5;

}

 

END {

              print a;

}

 

として、

$ ls –al | awk –f sum.awk

 

としてもよい。

 

AWKの区切り文字:

AWKのデフォルトの区切り文字は” “、つまり空白。

区切り文字で区切られたそれぞれの文字列をフィールドと言い、各行のフィールドは順番に$1, $2,...で取得できる。

また、フィールドの集合(つまり各行)はレコードと呼ばれる。

 

例えば、doi.txtが以下の内容で保存されていたとしよう。

(doi.txt)

Kotaro Male Father 35

Akiko Female Mother 42

Hin@ko Female Daughter 3

Yu!ko Female Daughter 1

 

これを名前だけ取り出す (フィールド1のみ取り出す) には、

$ awk '{print $1}' doi.txt

Kotaro

Akiko

Hin@ko

Yu!ko

 

さらにすごいのは、名前と年齢だけ取り出したい場合(フィールド1とフィールド4のみ取り出す)には、

$ awk '{print $1, $4 }' doi.txt

Kotaro 35

Akiko 42

Hin@ko 3

Yu!ko 1

 

でよい。定型化された文書の処理に対してものすごく強力である。

1$1$4をカンマで区切らない場合、結果は空白で区切られずに出力される。

$ awk '{print $1 $4 }' doi.txt

Kotaro35

Akiko42

Hin@ko3

Yu!ko1

 

2$0は特殊なフィールドで行全体を表す。またprintに引数を指定しない場合、AWKは自動的にprint $0を実行する。

 

組み込み変数NRNF

NR:現在処理中のレコードが、何番目(Number or Record)の行であるかを示すカウンタ。

NF:その行に、何個のフィールドがあるかの合計値。

$ awk '{print NR, $1, $4 }' doi.txt

1 Kotaro 35

2 Akiko 42

3 Hin@ko 3

4 Yu!ko 1

 

NR3以上のもののみ表示するには、

$ awk 'NR>=3 {print NR, $1, $4 }' doi.txt

3 Hin@ko 3

4 Yu!ko 1

 

正規表現でパタ-ン抽出:

下記条件(パタ-ン)指定部分に、/<正規表現>/として、正規表現を指定することが出来る。

$ awk '/Female/ {print NR, $1, $4 }' doi.txt

2 Akiko 42

3 Hin@ko 3

4 Yu!ko 1

 

AWKの構文:

実は、AWKは以下の構文からなると理解すると、一瞬で暗雲が晴れる。

$ awk ‘BEGIN {各行を処理する前の処理}<パタ-> {各行の処理} END {各行を処理した後の処理}

 

これがAWKの基本構文である。ただし、

ü         BEGIN{各行を処理する前の処理}」および「END {各行を処理した後の処理}」はどちらか一方または両方ともを省略することが出来る。

ü         <パタ->も省略可能。その場合、全ての行が同等に処理される。

ü         <パタ-> {各行の処理}」は何通りでも記述できる。たとえば、「<パタ-1> {各行の処理1 <パタ-2> {各行の処理2}」のように。

 

つまり、BEGINENDは各行の処理には直接の関係が無い。よくある言語のように、BEGIN {} ENDとして{}の間に各行の処理を書くのではない。(筆者は最初これにはまった)

 

例)

$ awk 'BEGIN {print "***** Doi Start *****"} /Female/ {print $1} /Male/ {print $4} END {print "***** Doi End *****"}' doi.txt

***** Doi Start *****

35

Akiko

Hin@ko

Yu!ko

***** Doi End *****

 

フィールドセパレータ(FS)の変更:

組込文字FSにて、フィールドセパレータの変更が可能。また、オプション-Fにても同様のことが可能。

例)FSを用いた場合

$ awk 'BEGIN {FS = ","} {print $1}' doi.csv

Kotaro

Akiko

Hin@ko

Yu!ko

 

例)-Fを用いた場合

$ awk -F , '{print $1}' doi.csv

Kotaro

Akiko

Hin@ko

Yu!ko

 

ちなみに、doi.csvは、

$ awk '{gsub(/ /, ","); print}' doi.txt > doi.csv

Kotaro,Male,Father,35

Akiko,Female,Mother,42

Hin@ko,Female,Daughter,3

Yu!ko,Female,Daughter,1

 

とすれば、作れる。gsub()は、

              gsub(regexp, replacement, target)

という引数をとり、target文字列中で、regexpにマッチする部分を全てreplacementに置き換えて、その置き換え回数を返す組込関数である。

 

ほかにも、

index(in, find)

文字列inの中で、findが出てくる最初の場所を検索し、見つかった文字列が始まるinのキャラクタの位置を返す。

length(string)

文字列stringの長さを返す。

match(string, regexp)

stringから、正規表現regexpにマッチする部分文字列の中で、最も左にあり、もっとも長い部分文字列を検索し、

部分文字列が始まる場所を返す(stringの最初から始まっていれば1)。マッチするものが見つから なかった場合、0が返る。

split(string, array, fieldsep)

stringfieldsepによって分割し、分割された結果を arrayに格納する。 分割されたi番目の要素はarray[i]に格納される。

                            splitは作り出された要素の数を返す。

sprintf(format, expression1,...)

printfがその引数を渡されたときに出力するであろう文字列を (出力はせずに)返す。

sub(regexp, replacement, target)

sub関数はtargetの値を変更する。検索する値は文字列でなければならず、そしてそれは regexp

                            与えられる正規表現にマッチする部分文字列の中で一番左にあり、長さが一番長いものである。

                            文字列全体の内マッチしたテキストは replacementで置き換えられる。

gsub(regexp, replacement, target)

subと似ているが、gsubはもっとも長く、最も左にあり、それぞれが重ならないようなマッチした

                            部分文字列をすべて置換する。 gsub`g'は全ての場所で置換を行う"global"を意味する。

substr(string, start, length)

string中でstart番目のキャラクタから始まる長さ lengthの部分文字列を返す。

                            文字列の最初のキャラクタの数は1である。

tolower(string)

与えられたstring中の大文字を小文字に置き換えた文字列を返す。

toupper(string)

与えられたstring中の小文字を大文字に置き換えた文字列を返す。

 

などの文字列操作関数があるが、詳細は、

http://www.kt.rim.or.jp/~kbk/gawk/gawk_13.html

などを参照されたい。

 

AWKの書式化:

awkでもprintf文が使えて、Cのそれとそっくりである。

例)

printf("%d", $1);     $1を十進数で書式化)

詳細は他書に譲る。

 

AWKの制御文:

if (condition) { statement } [ else { statement } ]

while (condition) { statement }

do { statement } while (condition)

for (expr1; expr2; expr3) { statement }

等、Cでおなじみの制御文がAWKでも使える。

 

さらに、演算子

<><=>=== !=&&||!

などの使用もOK。一つうれしいのは、演算子

              ~(チルダ)

にて、

              if (string ~ /regexp/) {

                            statement

              }

などとして、string(文字列)とregexp(正規表現)での比較が可能。その際、string~の左辺に、regexp~の右辺に配置する必要がある。

例)

# if.awk

{

              if ($0 ~ /Female/) {

                print;

        }

}

 

AWKの配列:

AWKでも配列が使える。

例)

#array.awk

{

    n = split($0, arr);

    for (i = 1; i <= n; i++) {

        print arr[i];

    }

}

 

さらにすごいのは、添え字に文字列が使えることである。これによりいわゆる連想配列(マップ)の構築が可能となる。

たとえば、doi.txtの中の男性(Male)と女性(Female)の人数の合計をそれぞれ表示したい場合、

#map.awk

{

    gender[$2]++;

}

END {

    for (i in gender) {

        print i ": " gender[i];

    }

}

 

特殊な構文(for i in gender)により、gender連想配列で使われている添え字(Mapでいうkey)をiにて列挙し、全て処理することが出来る。値(Mapでいうvalue)はgender[i]にて抽出可能。

 

自作関数:

自作の関数が定義できる。

)

#func.awk

BEGIN {

    print add(1, 2);

}

 

function add(a, b) {

    return a + b;

}

 

function--ドで宣言、関数名(引数)で宣言できる。引数および戻り値の型を宣言する必要はなく、どんな型(数値、文字列、配列)でも渡せて、数値または文字列を返すことが出来る。

 

変数宣言:

通常の変数と参照変数の定義が可能。

a = $1     (通常の変数)

$a = $1   (参照変数)

 

この場合、$1を変更すると、aは変わらないが、$a$1と同じ値になる。

 

コマンドライン変数:

-vでコマンドラインからスクリプトに変数が渡せる。

例)

$ awk -v VAR=DOI '{print VAR, $1}' < doi.txt

DOI Kotaro

DOI Akiko

DOI Hin@ko

DOI Yu!ko

 

(参考サイト)

http://lagendra.s.kanazawa-u.ac.jp/ogurisu/manuals/awk/intro/