授業資料/06 の変更点


#CONTENTS

//  第 6 回 -- フィルタ II (Awk)

* プログラマブルなフィルタ [#te42a9a6]

前回までにほぼ単機能なフィルタについて学んだので,ここでは「プログラミングが可能な」フィルタについて学ぼう.

* awk: a programable filter: データ処理専用? フィルタ [#fa32c433]

awk はプログラムが可能なもっとも簡潔なフィルタの一つである.
使用目的としては,

+ 行を読み込み、
+ 中身を分割し、
+ 処理を施す

という作業にぴったりに出来ている.

&br;&br;
もちろん,perl, python, ruby などのプログラミング言語を用いればもっと柔軟に高度なことができるが,
awk の真髄は 
''簡単なデータ処理が簡単にできる'' 
ことにある.
理系学生にとってデータ処理は頻繁に必要な作業であるので,awk が使えると嬉しい場面が多い.

&br;&br;
CENTER:&size(24){''awk でデータ処理が簡単に!''};

&br;&br;
** How to launch the awk: awk の起動, 使い方 [#h0a3acdf]

awk の使い方(起動方法)には次の 3種類がある。&br;
CENTER:&ref(./awk-launch.png);
CENTER:&ref(./awk-launch.png,150%);

&br;
(注1) すぐあとで学ぶ [[&ref(/materials/JNorth_arrow-right-sm.png); スクリプトファイルのコマンド化>#script-command]] がしてあればよい.
&br;
&ref(/materials/warning.png); スクリプトとは動作指示書, すなわち,プログラムのことだと思って良い.

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

&br;&br;
** The form of awk scripts: awk スクリプトの形 [#h53aad1e]

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

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

という構造をしている.

&br;&br;
&ref(/materials/warning.png); awk 世界での専門用語. awk のマニュアルなどでは,以下のような用語がつかわれることがある.

: ''パターン'' | 
処理対象となる行の指定方法をいう.
: ''アクション'' |
対象行に対する一連のコマンド(処理)をこうよぶ.

&br;&br;
** Actions of awk: awk の動作 [#j4bc8b63]

awk は次のように動作する.

+ まず、データとして行を一行読み込み,
++ 最初のパターンと照合して、
--- 処理対象となる… 対応するアクションを適用し、
--- 処理対象とならない…なにもしない、
&br;&br;
++ 次のパターンと照合して,
--- 処理対象となる… 対応するアクションを適用し、
--- 処理対象とならない…なにもしない、
&br;&br;
++ さらに次のパターンと照合…
--- 処理対象となる… 対応するアクションを適用し、
--- 処理対象とならない…なにもしない、
&br;&br;
++ (上の作業を繰り返して…)
&br;&br;
++ 照合するパターンが無くなったらこの行のデータ処理は終了.
&br;&br;
+ データとして次の行を読み込む.
&br; …
&br; ''以下繰り返し.''

&ref(/materials/Gnome-Preferences.png); 実行例.
上の繰り返しの意味がつかみにくい人は,この例を見てみよう.
&br;
例えば,以下の中身をもつ test.awk というファイル
// programu source 表記
#highlighter(language=SH,number=on,cache=on){{
{
    print $1,$2,$3
}
{
    print $1,$2
}
}}

を用意して,

> ''echo␣"a␣b␣c␣d"␣|␣awk␣-f␣test.awk''

と実行すると,

  a b c
  a b

という結果が得られる.
データ行に対して,二つのパターン(無条件パターン)がマッチして,それぞれでアクションが行われたことが分かる.

&br;&br;
** Patterns of awk: awk のパターン [#vb17d0e3]

awk のパターンはまあ普通は以下の6種類と思えば良い.

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

&br;&br;
** Data decomposition: データの分解 [#v338d161]

awk が読み込んだデータをどう分解するかを知っておこう.
&br;
まずマニュアルに出てくる用語から.

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

レコードをフィールドに分解する時の「区切り」は,デフォルトでは「空白(連続しても良い)」である。
区切りに使う文字を変更して指定するには、以下の方法がある.

- 起動時にオプションで指定する。
&br;
… 
''-F'' 
というオプションを使うと、フィールドの区切りを変えられる。
&br;&br;
- プログラム中で指定する。
&br;
… 
プログラムの途中で、''FS'' という変数を書き換えると、それが新たなフィールドの区切りになる。

&ref(/materials/notes.png); 実習: フィールドの区切りを変えてみよう。

まず、何も設定しないと、

> ''ls␣-lg␣|␣awk␣'{print␣$7}'''

とするとファイル名が出力され(''$7'' ではなく ''$8'' などの場合もあるので自分の環境にあわせて考えよう),

> ''ls␣-lg␣|␣awk␣'{print␣$1}'''

とすると、パーミッション情報が得られる。
&br;
&br;
では、フィールドの区切りの文字を ''x'' という文字に変えてみよう。
上に書いたように,次の二つの方法がある。

> ''ls␣-lg␣|␣awk␣-Fx␣'{print␣$1}'''

> ''ls␣-lg␣|␣awk␣'BEGIN{FS="x"}␣{print␣$1}'''

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

&br;&br;
** Simple grammer: 簡単な文法 [#wda773b1]

*** Special variables: 特別な変数 [#xcc960d2]

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


*** String operations: 文字列操作 [#d66bf5cb]

: 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 の大文字化。

*** Calculations: 演算 [#h7bcd9c8]

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

*** Flow control: 制御構造 [#i37f854c]

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

*** Arrays: 配列 [#s9185c43]

配列とは多変数をつなげて一つにしたもので,ベクトルのようなものだ.
&br;
''普通の配列''は,各要素は「数字で」指定する.
&br;  
''連想配列'' は,各要素を「文字列で」指定する.
&br;&br;
&ref(./array.png,70%);
CENTER:&ref(./array.png,80%);


&br;&br;
CENTER:&size(24){''「3番目の」箱には何が入ってますか? と聞けるのが普通の配列''};

CENTER:&size(24){''「黄色い」箱には何が入ってますか? と聞けるのが連想配列''};
&br;&br;

&ref(/materials/OK.png); 連想配列がとても便利な場面も多い.そういうときは積極的に使おう.


&br;&br;
&ref(/materials/Gnome-Preferences.png); 実行例 (1)
&br;
例えば,awk で「ファイルの名前と大きさを配列に記録しておいてあとで処理する」ことを考える.
簡単のため,今回はこの「処理」を単に「表示」するだけとすると以下のようになる.
&br;
: 普通の配列では… |
// programu source 表記
#highlighter(language=SH,number=on,cache=on){{
{
  size[NR-1]=$4
  name[NR-1]=$7
  }
END{
  for (i=0; i<NR; i=i+1) print name[i]"'s size is " size[i]
  }
}}
//
という内容のファイルを用意し(''$7'' ではなく ''$8'' などの場合もあるので自分の環境にあわせて考えよう),''test.awk'' という名前で保存してから,
//
> ''ls&#9251;-lg&#9251;|&#9251;awk&#9251;-f&#9251;test.awk''
<
とすると
//
  's size is
  Text_Highlighter-0.7.1.tgz's size is 137135
  moin-1.7.2's size is 4096
  moin-1.7.2.tar.gz's size is 5524184
  package.xml's size is 11639
  pukiwiki-1.4.7_notb's size is 4096
  pukiwiki-1.4.7_notb.tar's size is 1116160
  pukiwiki.ini.php's size is 18123
  pukiwiki.ini.php.2007's size is 18211
  test.awk's size is 105
//
というような結果が得られる.
&br;
&ref(/materials/warning.png); 関連する情報を処理するのにわざわざ二つの異なる配列を用意しないといけない「無駄」「まずさ」や,本質的に添字に数字を使う必要がないのに使っていることなどに注目しよう.
これらは,プログラムに「ミス」を呼びこむ罠となりうる.
&br;&br;
: 一方,連想配列では… |
// programu source 表記
#highlighter(language=SH,number=on,cache=on){{
{
  size[$7]=$4
  }
END{
  for (f in size) print f"'s size is " size[f]
  }
}}
//
という内容(''$7'' ではないかもしれないことは上同様)で同じことが出来る.
&br;
&ref(/materials/warning.png); 論理的にも無駄がなくなり,分かりやすく,間違えにくくなっていることに注目しよう.

&br;&br;
&ref(/materials/notes.png); 実習

上のスクリプトを実際に作成し,動作させてみよ.

&br;&br;
&ref(/materials/Gnome-Preferences.png); 実行例 (2)

> ''ls&#9251;-lg&#9251;|&#9251;awk&#9251;&apos;/x/{print&#9251;$0}&apos;''

とすると,これは

> ''ls&#9251;-lg&#9251;|&#9251;grep&#9251;x''

と同じである.

&br;&br;
&ref(/materials/Gnome-Preferences.png); 実行例 (3)

> ''ls&#9251;-lg&#9251;|&#9251;awk&#9251;&apos;length($7)>10{print&#9251;$7}&apos;''

とすると(''$7'' ではないかもしれないことは上同様),ファイル名が10文字以上のファイル名が出力される.

&br;&br;
&ref(/materials/Gnome-Preferences.png); 実行例 (4)

> ''ls&#9251;-lg&#9251;|&#9251;awk&#9251;&apos;$4>1000{s=s+$4}&#9251;END{print&#9251;s}&apos;''

とすると,ファイルの大きさが 1000バイト以上のファイルの大きさの合計が出力される.

&br;&br;
&ref(/materials/notes.png); 実習

+ 上の実行例を理解するとともに,実行してみよ.
//
+ 次の動作を理解し,隣の人に解説してみよう.&br;
&nbsp;&nbsp;&nbsp;きちんとした unix 環境の場合:
//
> ''ps&#9251;axu&#9251;|&#9251;awk&#9251;&apos;$2>2000{print&#9251;$2,$1,$NF}&apos;&#9251;|&#9251;sort&#9251;-n''
<
&nbsp;&nbsp;&nbsp;cygwin 環境などの場合:
//
> ''ps&#9251;axu&#9251;|&#9251;awk&#9251;&apos;$1>2000{print&#9251;$1,$NF}&apos;&#9251;|&#9251;sort&#9251;-n''


&br;&br;
* Command-ize of scrips: スクリプトファイルのコマンド化 [#xca9701d]
* Command-ize of scrips: スクリプトファイルのコマンド化 [#script-command]

シェルの alias などで体験したように,
unix では自分でコマンドを作る方法が豊富に用意されている.

&br;
その豊富な方法の一つとして、
''awk + スクリプト'' のような組み合わせを一つのコマンドのように見せることができる。
それがスクリプトのコマンド化である。
これによって

&br;&br;
CENTER:&size(24){''複雑な処理をコマンド一つで呼び出せるように!''};
&br;&br;

できるのだ.

&br;&br;
** How to command-ize scrips: スクリプトをコマンド化する具体的な方法 [#u1e45654]

やるべきことは以下の二つ.

+ スクリプトの先頭行に、
//
> ''#!プログラム名 (と必要なオプション)''
<
を書き込む.
&br;
&ref(/materials/Gnome-Preferences.png); awk の場合:&br;
> ''which&#9251;awk''
<
として,まず awk がどこにあるか調べておく.
おそらく ''/usr/bin/awk'' か ''/bin/awk''  だろう.
この調べがついたら,例えば ''/usr/bin/awk'' にある場合は,
対象のスクリプトファイル(例えば ''test.awk'')の先頭行に
//
> ''#!/usr/bin/awk&#9251;-f''
<
と書きこめばよい.
&br;
&ref(/materials/warning.png); awk がどこにあるかは環境によって異なる.必ず調べよう.
&br;
&ref(/materials/warning.png); オプション ''-f'' は重要だ.忘れないように.
//
&br;&br;
+ スクリプトファイルに,「実行してよい」と許可を出しておく.
&br;
対象のスクリプトファイルを例えば ''test.awk''としておこう.
そして,そのファイルのあるディレクトリで,
//
> ''chmod&#9251;u+x&#9251;./test.awk''
<
とすればよい.


&br;&br;
&ref(/materials/Gnome-Preferences.png); スクリプトファイルのコマンド化の例 (1)
&br;
例えば,ファイル ''test.awk'' を
// programu source 表記
#highlighter(language=SH,number=on,cache=on){{
#!/usr/bin/awk -f
{
  word = word + NF
  }
END{
  print NR, word
  }
}}
という内容にして,実行許可を出しておく.
&br;
そうしておいて,適当な文章が入ったファイル ''dummy.txt'' に対して,

> ''cat&#9251;dummy.txt&#9251;|&#9251;./test.awk''

という動作と,

> ''cat&#9251;dummy.txt&#9251;|&#9251;wc''

の動作の結果を比べてみよう.
&br;
&ref(/materials/warning.png); これは ''wc'' コマンドもどきを手作りしたことになる.

&br;&br;
&ref(/materials/warning.png); 考えてみよう.これがどういうことなのかというと、例えてみると次のような絵で表せる。

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

&br;&br;
&ref(/materials/Gnome-Preferences.png); スクリプトファイルのコマンド化の例 (2)
&br;
// fsize というファイル名のスクリプトを作る.
// 内容は以下の通り.
// // programu source 表記
// #highlighter(language=SH,number=on,cache=on){{
// #!/usr/bin/awk -f
// /^-/{
//   sum=sum+$4
//   count=count+1
//   }
// END{
//   print "Total size is ", sum, "for " count " files here."
//   }
// }}
// このスクリプトに実行許可を出してから,
// > ''ls -lg | ./fsize''
// とすると,
&br;
もらうデータの最初の項目の数字をどんどん合計して平均を出すコマンド ''average'' を作ってみよう.
具体的には,
// programu source 表記
#highlighter(language=SH,number=on,cache=on){{
#!/usr/bin/awk -f
{
  sum=sum+$1
  }
END{
  print sum/NR, sum, NR
  }
}}
という中身で作れば良い.
&br;
うまくいったならば,例えば,''dummy.dat'' というファイルを
// programu source 表記
#highlighter(language=SH,number=off,cache=on){{
1.0
10.0
3.2
5.3
4.0
1.8
}}
という中身で用意して,

> ''cat&#9251;dummy.dat&#9251;|&#9251;./average''

とすれば,

  4.21667 25.3 6

として,平均,合計,総数 が出力されるはずだ.



&br;&br;
&ref(/materials/notes.png); 実習
&br;
+ 上の実行例を理解するとともに,実行してみよ.
&br;
+ 「平均」を求めるコマンドを参考にするなどして,「平均」と「分散」を出力するコマンドを作ろう.



&br;&br;
* Report: レポート [#w3be2279]

以下の課題について能う限り賢明な調査と考察を行い,
&br; 
''AppliedMath7-Report-06''
&br;
という題名をつけて e-mail にて教官宛にレポートとして提出せよ. なお,レポートを e-mail の代わりに TeX で作成した書面にて提出してもよい.  

** Excercises: 課題 [#ha981e00]

+ 上の「平均」と「分散」を出力するコマンドを作成し,実際に適当なデータファイルを作り,それに対してその作業を行い,その結果を示せ.
もちろん,作成したコマンドの中身も記すこと.
&br;&br;
+ 今月の最終木曜日をもとめる awk スクリプトを組め.
ただし,入力データとして ''cal'' の出力を利用して良い.
&br;
実際に作成したスクリプトを紹介するとともに,その動作状況の様子を報告せよ.
&br;&br;
+ ''osaka&#9251;130.003.125.224'' のように,単語のあとに3桁の数字列が4つピリオドでくっついている文字列があるとする.
こういう入力データを,数字の部分だけ前後引っくり返して
''osaka&#9251;224.125.003.130'' と出力するように awk スクリプトを組め.
&br;
これも実際に適当なデータファイルを作り,それに対してその作業を行い,その様子を報告せよ.


&br;&br;
* about Icons, ClipArts [#v45b118a]

For details, see [[&ref(/materials/JNorth_arrow-right-sm.png); this>materials]].

// ━┃┏┓┛┗┣┳┫┻╋


// コマンドライン入力は「行頭を > で始める」.
// コマンドライン出力は「行頭をブランクで始める」.

// 実習アイコン
// &ref(/materials/notes.png); 

// 注意アイコン
// &ref(/materials/warning.png);

// Link アイコン
// &ref(/materials/JNorth_arrow-right-sm.png);

// OK アイコン
// &ref(/materials/OK.png);

// NG アイコン
// &ref(/materials/NG.png);

// サンプルアイコン
// &ref(/materials/Gnome-Preferences.png);

// 大文字での強調 
// CENTER:&size(24){''ほげほげ''};

// programu source 表記
// #highlighter(language=ruby,number=on,cache=on){{}}

// パイプ
// &brvbar;