第 6 回 (2006.05.26) -- フィルタ II (awk)

awk

awk は行単位でデータを読み込み、 1行の中身を適当な項目に分割して、 その項目ごとに複雑な処理を施すことを主な使い方とする、 プログラマブルなフィルタである。
unix ではデータはテキスト形式で保存されていることが多く、 そのデータは意味のあるひとかたまりが一行に固められて保存されている形式が多い。 であるから、そうしたデータを処理するには awk はまさにぴったりのフィルタである。 このため、unix でデータを処理する時には昔から awk が非常に良く使われてきた。
より柔軟なプログラムが可能である perl が登場してからは awk の登場の出番も少なくなったが、 ごく簡単な処理をするにはやはり awk を使う方が楽だったりするので、 フィルタのプログラミング入門という意味でも awk から学ぶのが良いだろう。
また、awk に慣れ親しんでおけば、perl を学ぶときもあまり違和感がないので、 そういう意味でもやはり awk を学んでおくのは無駄にならない。

awk の起動(使い方)

awk はやはり他のフィルタと同じく、シェルから起動して用いる。 awk の使い方というか、起動方法には次の 3種類がある。

awk の起動方法(呼び出し方) 注釈
how to launch awk (注1) ファイル中の先頭行に

#!/bin/awk -f … linux の場合
#!/usr/bin/awk -f … FreeBSD の場合

と書き込んであればよい。 これについては、 スクリプトファイルのコマンド化 を参照せよ。

ここでいう「スクリプト」が,awk の動作を指定するためにわれわれが用意するプログラムのことであるのは sed のときと同様である。

awk スクリプトの基本構造

awk は入力を一行ずつ処理していく. つまり,処理単位は「行」である. これは sed 同様である。 そして, awk のスクリプトは基本的に,

      処理対象となる行の指定 その1{
        コマンド
        コマンド
        コマンド
        コマンド
        }
      処理対象となる行の指定 その2{
        コマンド
        コマンド
        コマンド
        コマンド
        }
      …
      

という形を構造をしている. この時、awk では、 処理対象となる行の指定を パターン, それに対応する(複数の)コマンドをアクションと呼ぶ。
そして,awk の動作は、次のようになる。 まず、データとして行を一行読み込んでは、 最初のパターンと照合して、

と動作して、今度は次のパターンと照合して同様に動作して、以下これを繰り返し、 スクリプトの最後までいく。
そうしたら、新たに次の行をデータとして読む。 そして、またスクリプトの最初からチェックを始めて最後までいく。
そうしたら、新たに次の行をデータとして読む… を繰り返すというのが基本動作である.

awk のパターンは大きく分けて次の 6つがある。

■ awk のパターン ■
パターン 説明
全部該当。
パターンとして何も書かないと、こうなる。
BEGIN データを読みこむ前に一回だけ該当する、 という特別なパターン。
END 全てのデータ読み込みとその処理が終わった後に一回だけ該当する、 という特別なパターン。
/正規表現/ 指定された正規表現を「含む」行が該当する。
論理式 指定した論理式が成り立てば、該当する。
パターン1, パターン2 パターン1 が該当する行から、パターン2 が該当する行までの範囲の行、 が該当する。

データの分解

まず、awk が読み込んだデータをどう扱うかを知っておこう。
awk がデータを一行ずつ読み込み、それを複数の項目に分解する、というのは既に述べた。 これは全く自動的に行われる。 ちなみに、次のような単語が使われるので示しておこう。

レコード
読み込まれた一行のデータ。
フィールド
レコードを複数の項目に分解した時の、その項目。
レコード中の n 番目のフィールドは $n で表せる。
ただし、 $0 は特別に全てのフィールド、つまり、レコードを指す。

この「レコード」→「フィールド」の過程でどういうルールによって分解されるのかだが、 これは簡単で、「適当な区切りで分ける」のである。
この時の「適当な区切り」とは、何も指定しないと「空白(連続しても良い)」である。 区切りに使う文字を変更して指定するには、

実習

フィールドの区切りを変えてみよう。 まず、何もしないと、
ls -lg | awk '{print $9}'
ではファイル名が得られる.
ls -lg | awk '{print $1}'
とすると、パーミッション情報が得られる。

では、フィールドの区切りの文字を" A " という文字に変えてみよう。 それには、次の二つの方法がある。

ls -lg | awk -FA '{print $1}'

ls -lg | awk 'BEGIN{FS="A"} {print $1}'

各々やってみよ. また,他の区切り文字も試してみよ.

awk の簡単な文法

特別な変数

FS
入力レコードをフィールドに分解するときに使う区切り文字.
ARGC
awk 起動時の引数の個数.
ARGV
awk 起動時の引数を並べた配列.
n 番目の引数は,ARGV[n-1] である. よって,引数は ARGV[0] から ARGV[ARGC-1] まであることになる.
また,1番目の引数,つまり ARGV[0] はコマンド自身,つまり awk である.
NF
現在のレコードのフィールド数.
NR
その時点での全レコード数. 要するに,そこまで読み込んだ入力データの行数.

文字列操作

gsub(r, s)
正規表現 r にマッチする部分を全て s に変換。
index(s, t)
文字列 s 中に含まれる文字列 t の位置。
length(s)
文字列 s の長さ。
match(s, r)
文字列 s 中で正規表現 r にマッチする位置。
split(s, a [, r])
文字列 s を正規表現 r を用いて分割して配列 a に入れる。 r を省略すると FS を用いる(つまり、フィールド分割と同じになる)。
substr(s, i, [,n])
文字列 s の i 番目から最大 n 文字の(部分)文字列を返す。
tolower(str)
文字列 str の小文字化。
toupper(str)
文字列 str の大文字化。

演算

+ - * / ^ %
四則演算、べき乗、剰余。
== != < > <= >=
同値、非同値、より小さい、より大きい、以下、以上。
(注) 最初の記号は"=" が二つつながっている。
=
代入.
(注) 記号は"=" が一つ. 同値と違うので注意.
! && ||
NOT AND OR
~ !~
正規表現マッチ、否定のマッチ。
in
配列に属する。
atan2(y, x)
y/x の逆 sin 関数.
exp, cos, sin, log, sqrt
ごく普通の関数.
int(式), rand()
整数への切り捨て,乱数(0〜1 の間)

制御構造

if (条件) 条件が正しい時の処理 [ else 条件が正しくない時の処理 ]
条件が正しいかどうかチェックして,それによって処理を変える.
for (初期化 ; ループ条件 ; ループ毎処理) 処理
繰り返し. 初期化を行ってから,ループに入る. 各ループでは,ループ条件が満たされれば,処理を行ってからループ毎処理を行い,もう一度ループに入る. ループ条件が満たされない場合には,ループ終り.
for (変数 in 配列) 処理
繰り返し. 変数を自動的に一通り変えていって,そのたびに処理が行われる.

配列

配列とは,添字のついた変数で,添字を指定されると一つのデータがでてくるものである. ベクトルのようなもの,と思えば良い.
awk には「連想配列」という,添字として文字列を使える配列が実装されている. これは,例で示した方が分かりやすいだろう.

awk で配列を用いた例
たとえば,

        #!/bin/awk -f
        { size[$9]=$5 }
        END{ for (file in size) print file "'s size is " size[file] "." }
  

というスクリプトを"fsize"という名前で用意して,実行許可を与えてから
ls -lg | ./fsize
などとするとどうなるだろうか.

実習

上のスクリプトを実際に作成し,動作させてみよ. ただし,後述の スクリプトファイルのコマンド化 を理解してからの方がよいだろう.

awk を使った他の例

さて,慣れてないと上のような抽象的な説明では良く分からないだろう. よって,簡単な例をやってみて実感するのがよいだろう. いくつか例を挙げてみるので,自分で体感してみよ.

ls -lg | awk '/hoge/{print $0}'
とすると,これは ls -lg | grep hoge と同じである.

ls -lg | awk ' length($9) > 10 {print $9} '
とすると,ファイル名が10文字以上のファイルのリストができる.

ls -lg | awk '$5 > 1000 { s = s + $5 } END { print s }'
とすると,ファイルの大きさが 1000バイト以上のファイルの大きさの合計がでる.

実習

  1. 上の実行例を理解するとともに,実行してみよ.

  2. ps -axu | awk '$2 > 7000 {print $2,$1,$NF}' | sort -n はどんな動作をするか. 隣の人に解説してみよ.

スクリプトファイルのコマンド化

unix には自分でコマンド(っぽいもの)を作る方法が豊富に用意されているのは、 シェルの alias などで体験済みのはずである。
その豊富な方法の一つとして、 あるプログラムに対するスクリプト自身を一つのコマンドのように見せることができる。 それがスクリプトのコマンド化である。
# これは、正しくは「インタプリタのスクリプトとインタプリタを結び付ける」ことを意味する。

具体的には、これはスクリプトの先頭行に、

#!プログラム名

と書き込めば良い。 こうすると、(実行許可を与えておけば)そのファイル自身が新たにコマンドとして認識され、 そのファイル名をコマンドとして実行すると、

プログラム名 スクリプトファイル名

と入力したことと同等となる。
少し例をみてみよう。

スクリプトファイルのコマンド化の例

        #!/bin/grep dummy
        dummy ha kore
        are ha dore
        aaa bbb ccc
        123 dummy datoka
        shikaraba
    

と書き込んだファイルを用意する。ファイル名を " script-test " としてある、としよう。 で、このスクリプトファイルに「実行許可」を与える。具体的には、
chmod u+x script-test
と一回やっておけばよい。 すると、このスクリプトファイル " script-test " は「新しくコマンドとして扱われる」。
実際に実行してみよう。

% script-test

#!/bin/grep dummy
dummy ha kore
123 dummy datoka


という結果が得られるのがわかる。 これはよく考えてみると、

% grep dummy script-test

としたことと同等である。

これがどういうことなのかというと、例えてみると次のような絵で表せる。

■ スクリプトのコマンド化を例えるなら… ■
make a command from script 俳優が台本に従って演技するとき、それは俳優自身ではなくて演劇の役として行動していることになる。 つまり、台本にしたがっているならば役の名前で呼ばれる人になっている、 といえる。
これと感覚的には同じことである。 つまり、複雑で長いスクリプトを用意して、それに従ってコマンドが動作するとき、 人間からみればその動作の意味はスクリプトで決まるのである。
よって、スクリプトの名前でそれをコマンド化できれば、 人間にとって直感に非常にあうので、わかりやすく、かつ、便利になる、 というわけである。



他の例もみてみよう.

スクリプトファイルのコマンド化の他の例
fsize-sum というファイル名のスクリプトを作る. 内容は以下の通り.

    #!/bin/awk -f
    BEGIN {
      sum = 0;
      count = 0;
      }
    /^-.*$/ {
      sum += $5;
      ++count;
      }
    END {
      print "Total size of ",count " files in this directory is", sum, "byte.";
      }
  

このスクリプトを実行できるようにしてから,
ls -lgA | ./fsize-num
などとすると,「そのディレクトリにあるファイルの総サイズ」が表示される.

実習

上の実行例を理解するとともに,実行してみよ.

レポート課題(25点)

以下に示された課題について

AppliedMath-Report-06

という題名をつけて e-mail にて教官宛にレポートとして提出せよ(教官のメールアドレスは授業中に口頭で伝える).
なお,レポートを e-mail の代わりに TeX で作成した書面にて提出してもよい.

課題内容

  1. sed と同じ動作をするように awk を使ってみよ.
    また,実際に適当なデータファイルを作り,それに対してその作業を行い, その様子を(script コマンドのログなどを用いて)報告せよ.

  2. 今月の最終木曜日をもとめる awk スクリプトを組め. ただし,入力データとして cal の出力を利用して良い.
    実際に作成したスクリプトを紹介するとともに,その動作状況の様子を(script コマンドのログなどを用いて)報告せよ.

  3. osaka 130.003.125.224
    のように,単語のあとに3桁の数字列が4つピリオドでくっついている文字列があるとする. こういう入力データを,数字の部分だけ前後引っくり返して osaka 224.125.003.130 と出力するように awk スクリプトを組め.

    実際に適当なデータファイルを作り,それに対してその作業を行い, その様子を(script コマンドのログなどを用いて)報告せよ.




最終更新日 … $Date: 2006/05/06 17:50:21 $
Valid CSS! Valid XHTML 1.1!