典型的なawk
プログラムでは、すべての入力は標準入力(デフォルトでは
キーボードだが、ほとんどの場合は他のコマンドからのパイプ)かコマンドライ
ンで指定した名前のファイルのどちらかから読み込まれる。入力ファイルを指定
している場合、awk
は指定した順番に従ってすべてのファイルからデータ
の読み込みを行う。ある時点で処理している入力ファイルのファイル名は組込み
変数FILENAME
(セクション 組み込み変数を参照)から得ることができる。
入力はレコードと呼ばれる単位で読み込まれ、プログラムに記述されたル ールに従い、一度に一つのレコードを処理する。デフォルトでは各レコードはひ とつの行である。各レコードはフィールドと呼ばれる塊に自動的に分割さ れる。これはあるレコードの一部分について作業を行うプログラムに便利である。
まれに特別な場合に、getline
コマンドを使う必要にせまられることがあ
るだろう。getline
コマンドは任意の数のファイルから陽に入力を行うこ
とを可能にし、コマンドライン上で名前を指定しなくてもファイルから読み込み
ができるので重宝する。
(セクション getline
を使った入力を参照).
awk
ユーティリティはawk
プログラムに対する入力をレコードとフ
ィールドに分割する。レコードはレコードセパレータと呼ばれるキャラク
タによって分割される。デフォルトでは、レコードセパレータは改行キャラクタ
である。これはデフォルトではレコードが単一の行であるからである。組込み変
数のRS
に値をセットすることによって、レコードセパレータを別のキャ
ラクタにすることができる。
他の変数と同じ様に、代入演算子`='
(セクション 代入式を参照)を使ってawk
プロ
グラム中でRS
の値を変更することも可能である。
新しいレコードセパレータのキャラクタは、それが文字列定数であることを示す
ために引用符で括ったほうがよい。ほとんどの場合、これを行うための正しいタ
イミングは実行開始時、入力がなんらかの処理を行う前、つまり、最初のレコー
ドがレコードセパレータを使って読み込まれる前である。これを行うために
code{BEGIN}スペシャルパターを使用する
(セクション 特殊パターンBEGIN
とEND
を参照)。
例えば、
awk 'BEGIN { RS = "/" } ; { print $0 }' BBS-list
この例では、入力を行う前にRS
に値を"/"
に変更している。
これはスラッシュが先頭にある文字列であり、結果として
レコードはスラッシュで分割される。その後で入力ファイルが読み込まれ、
awk
プログラムの二番目のルール(パターンのないアクション)
によって各レコードが出力される。各print
文はその出力の
最後に改行を追加するので、このawk
プログラムは
入力のスラッシュを改行に変換してコピーする。次にこのプログラムを
`BBS-list'に対して実行したときの結果を示す。
$ awk 'BEGIN { RS = "/" } ; { print $0 }' BBS-list -| aardvark 555-5553 1200 -| 300 B -| alpo-net 555-3412 2400 -| 1200 -| 300 A -| barfly 555-7685 1200 -| 300 A -| bites 555-1675 2400 -| 1200 -| 300 A -| camelot 555-0542 300 C -| core 555-2912 1200 -| 300 C -| fooey 555-1234 2400 -| 1200 -| 300 B -| foot 555-6699 1200 -| 300 B -| macfoo 555-6480 1200 -| 300 A -| sdace 555-3430 2400 -| 1200 -| 300 A -| sabafoo 555-2127 1200 -| 300 C -|
`camelot'BBSのエントリが分割されていないことに注意。 元のデータファイル (セクション 例で使用するデータファイルを参照)、 では、このような行だった。
camelot 555-0542 300 C
そこにはボーレートが一つだけしかなく、スラッシュがレコード中にない。
レコードセパレータを変更するもう一つのやり方は、コマンドライン上で 変数代入の機能を使うことである。 (セクション Other Command Line Argumentsを参照).
awk '{ print $0 }' RS="/" BBS-list
これは`BBS-list'を処理する前にRS
に`/'を
セットする。
`/'のような一般的でないキャラクタをレコードセパレータに
つかうことは、ほとんどすべての場合に正しい結果を生じる。
しかしながら、以下の様な(極端な)パイプラインでは、
意外にも`1'が出力される。
そこには改行からなる一つのフィールドがある。
組込み変数NF
の値はカレントレコードのフィールドの数である。
$ echo | awk 'BEGIN { RS = "a" } ; { print NF }' -| 1
入力ファイルの終端に達すると、
そのファイルの最後のキャラクタがRS
にあるキャラクタで
なくてもカレント入力レコードを終端する(d.c.)。
空文字列""
(キャラクタが何もない文字列)は、
RS
の値としては特別な意味を持っている。
それは、レコードを一行以上の空行で区切るということである。
詳しくは
セクション 複数行レコードを参照.。
awk
の実行中にRS
の値を変更した場合、
その新しい値は後続のレコードの区切りに使用されるが、
現在処理されているレコード(と既に処理されたレコード)
にはなんの影響も及ぼさない。
レコードの区切りが終了した後で、awk
は
RS
にマッチした入力中のテキストを
RT
という変数にセットする。
RS
の値は一文字に限定されるものではなく、任意の正規表現
(セクション 正規表現を参照)を使用することができる。
一般的には各レコードの終端にはその正規表現にマッチした文字列が続いており、
次のレコードはマッチした文字列の終端の次から始まる。この一般的なルールは
通常のRS
が改行だけからなっているようなときにはうまく働く。レコー
ドは次のマッチする文字列の先頭(入力の次の改行)で終わり、続くレコードはこ
の文字列のすぐ後(続く行の先頭のキャラクタ)から始まる。改行はRS
に
マッチするので、いずれのレコードの一部分にもならない。
RS
が単一のキャラクタであったとき、RT
はそれと同じキャラクタ
一文字になる。しかし、RS
が正規表現であるとき、RT
はもっと便
利になる。つまり、実際に正規表現にマッチした入力テキストが格納されるので
ある。
次に挙げる例はこれらの機能を両方とも説明している。RS
は改行にも、
(前後に連続した空白が付いてもよい)一つ以上の連続した大文字にもマッチする
正規表現がセットされている(セクション 正規表現を参照)。
$ echo record 1 AAAA record 2 BBBB record 3 | > gawk 'BEGIN { RS = "\n|( *[[:upper:]]+ *)" } > { print "Record =", $0, "and RT =", RT }' -| Record = record 1 and RT = AAAA -| Record = record 2 and RT = BBBB -| Record = record 3 and RT = -|
出力の最後の行は余計な空白行がある。
これは、RT
の値が改行で、さらにprint
文が
改行を最後につけているからである。
RS
に正規表現を設定したり、RT
を使った便利な
サンプルについてはセクション シンプルなストリームエディターを参照.
RS
はレコードの終端を決めるために用いられる。
RS
を正規表現として使用することと、RT
変数は
gawk
における拡張である。これらは互換モードでは使用する
ことはできない(セクション コマンドラインオプションを参照).。
互換モードでは、RS
の先頭のキャラクタのみがレコードの
終端を決定するのに使われる。
awk
は現在の入力ファイルから読み込んだレコードの数を記憶している。
その値はFNR
という名前の組み込み変数に記録されている。この
変数は新しいファイルを読み込み始めたときに0にリセットされる。
別の組み込み変数NR
は、すべてのデータファイルから読み込んだレコードの
数を記録している。この変数は0から始まるが、自動的に0にリセットされる
ことはない。
awk
が入力レコードを読み込んだとき、そのレコードは自動的に
インタープリタによって解析され、フィールドと呼ばれる塊に
分割される。デフォルトではフィールドの分割は行の中にある
単語のように、空白(whitespace)によって行われる。
awk
での空白は、スペースかタブ、あるいは改行の
一つ以上の連なりである(6)。
ほかの言語では空白とみなされるような改行やフォームフィードなどの
キャラクタは、awk
の場合は空白としてはみなされない。
フィールドの目的は、レコードのこういった塊を参照するのに
便利なようにするためであり、フィールドを使わなければならない
というものではない。望むならレコード全体を処理することもできる。
しかし、フィールドはパワフルなawk
プログラムを簡単にするのである。
awk
プログラム中でフィールドを参照するには、ドル記号`$'に
続けて参照したいフィールドの番号を続けることによっておこなう。
したがって、$1
は最初のフィールドを、$2
は二番目のフィールドを
参照する。例えば次のような行が入力されたとすると、
This seems like a pretty nice example.
この例の場合、最初のフィールドである$1
の内容は`This'である。
二番目のフィールド、$2
の内容は`seems'というようになる。
最後のフィールド$7
が`example.'であることに注意して欲しい。
これは、`e' と `.'の間にスペースがないためで、ピリオドは
七番目のフィールドの一部であるとみなされているのである。
NF
はカレントレコードにあるフィールドの数を保持する組み込み変数
である。awk
はレコードの読み込みのたびにNF
の値を自動的に
更新する。
フィールドが幾つあるかにかかわらず、あるレコードの最後のフィールドは
$NF
で表わすことができる。だから、先程の例でいうと、$NF
は
$7
と同じことであり、内容は`example.'になる。
これが動作するわけは後述する
(セクション 定数でないフィールド番号を参照)。
レコード中にフィールドが七個しかないときに
$8
のように最後の要素を越えて参照しようとしたときには、
空の文字列が返ってくる。
$0
は"0番目"のフィールドを参照するように見えるが、
これは特別なケースで入力レコード全体を表わす。$0
は
フィールドを気にしたくないときに使用する。
さらに幾つかの例を挙げよう。
$ awk '$1 ~ /foo/ { print $0 }' BBS-list -| fooey 555-1234 2400/1200/300 B -| foot 555-6699 1200/300 B -| macfoo 555-6480 1200/300 A -| sabafoo 555-2127 1200/300 C
この例では、`BBS-list'というファイル中のレコードで、
レコードの第一フィールドに`foo'という文字列を含んでいる
ものを出力している。
`~'という演算子はマッチング演算子(matching operator)
(セクション 正規表現の使い方を参照)と
呼ばれ、ある文字列(ここではフィールド$1
)と与えられた
正規表現とがマッチするかを検査する。
一方次の例は、レコード全体から`foo'を探して、 それが見つかった入力レコードの第一フィールドと最終フィールドを 出力する。
$ awk '/foo/ { print $1, $NF }' BBS-list -| fooey B -| foot B -| macfoo A -| sabafoo C
フィールドの番号は定数である必要はない。
あるフィールドを参照するために、awk
言語での
任意の式を$samp{$}の後で使うことができる。
その式の値はフィールド番号として扱われる。このとき式が数値ではなく
文字列であった場合、その式の値は数値へと変換される。
例を挙げよう。
awk '{ print $NR }'
NR
はそれまでに読み込んだレコードの数だということを思い出そう。
一番目のレコードでは1、二番目では2…というようになる。
したがってこの例は、一番目のレコードの一番目のフィールド、
二番目のレコードの二番目のフィール…と出力していく。
二十番目のレコードでは二十番目のフィールドが出力されることに
なるが、ほとんどの場合はレコードには20未満のフィールドしかないから、
空白行が出力されるだろう。
もう一つフィールド番号として式を使った例を挙げる。
awk '{ print $(2*2) }' BBS-list
awk
は(2*2)
という式を評価し、それからその値を出力すべきフィー
ルドの番号として使用する。`*'は乗算を表すので、2*2
という式を評
価すると4となる。括弧は`$'のオペレーションより先に掛け算を行わせるため
である。フィールド番号を指定する式で二項演算子を使うときは常にこのようにす
る必要がある。この例では`BBS-list'というファイル中の全ての行の、運営時
間(4番目のフィールド)を出力する
(awk
で使える演算子の全ては
セクション Operator Precedence (How Operators Nest)を参照.
に一覧がある)。
フィールド番号が0になるような式を与えると、その結果はレコード全体となる。だ
から、 $(2-2)
の値は$0
と同じである。負の数によるフィールド番号
の指定は許されおらず、もしそういったフィールドを参照しようとすると、
通常はawk
プログラムの実行が中断される(POSIXの標準では、負のフィールド
番号を参照したときの
動作は定義されていない。gawk
ではこれを通知して、
プログラムを終了する。他のawk
処理系では
違った動作をするかもしれない)。
セクション フィールドの検査を参照で述べられているように、
カレントレコードのフィールド数は組込み変数の
NF
(セクション 組み込み変数を参照)に格納されている。
$NF
という式は特別な機能ではなく、NF
を
評価して、その値をフィールド番号として使った結果である。
awk
プログラムの中でフィールドの構成を変更することが
でき、それによってawk
はカレント入力レコードが
変更されたと認識する(実際の入力には影響ない。awk
は決して
入力ファイルを変更しない)。
サンプルとその出力を例示する。
$ awk '{ $3 = $2 - 10; print $2, $3 }' inventory-shipped -| 13 3 -| 15 5 -| 15 5 ...
`-'記号は減算の記号であるので、このプログラムは
三番目のフィールド$3
に`$2 - 10'、つまり二番目のフィールド
の値から10を引いた値を
セットする(セクション Arithmetic Operatorsを参照.)。
それから二番目のフィールドと新しい三番目のフィールドが出力される。
これがきちんと動作するには、フィールド$2
のテキストは
数として意味のあるものでなければならない。
(フィールドの)文字列は算術演算を行うために数値に変換される。
引き算の結果(の数値)は文字列に変換されて、第三フィールドになる。
セクション Conversion of Strings and Numbersを参照.
あるフィールドの値を変更した(awk
がそう認識した)とき、
入力レコードのテキストは新しいフィールドが古いフィールドの
ところにあるように再演算が行われる。
そのため、$0
がフィールドの変更を反映して変化するのである。
したがって、次のプログラムは、
入力ファイルのデータの各行の第二フィールドが
入力ファイルにあったものから10を引いたものに置き換えられた
結果を出力するのである。
$ awk '{ $2 = $2 - 10; print $0 }' inventory-shipped -| Jan 3 25 15 115 -| Feb 5 32 24 226 -| Mar 5 24 34 228 ...
最大のフィールド番号より大きい、存在しないフィールドに対する代入も可能である。 例を挙げよう
$ awk '{ $6 = ($5 + $4 + $3 + $2) > print $6 }' inventory-shipped -| 168 -| 297 -| 301 ...
ここでは$6
が新たに作り出されて、 $2
, $3
, $4
,
$5
の各フィールドの合計が格納される。 The `+'は足し算の記号であ
る。 `inventory-shipped'というファイルを使って各月ごとの積込量の合計を
$6
に設定する。
新しいフィールドを作成すると、awk
内部にあるカレントレコードのコピー
が変更される。したがって、フィールドを付け加えた後で`print $0'を出力す
るとその結果には新しいフィールドが含まれている。
この再計算の効果と再計算を行う機能についての説明はまだされていない。フィー
ルドの区切りを指定する出力フィールド指定子OFS
や NF
(フィール
ドの数セクション フィールドの検査を参照)についての説明もまだである。例
えば、NF
の値は新たに作り出されたフィールドのフィールド番号の最大のも
のがセットされる。
しかし気をつけなければならないのは、範囲外のフィールドにたいする
参照は$0
の値もNF
の値も変更しない
ということである。
範囲外のフィールドに対する参照は単に空文字列を生成するだけである。
例を挙げよう。
if ($(NF+1) != "") print "can't happen" else print "everything is normal"
これは`everything is normal'が出力されるはずだ。
なぜなら、NF+1
は範囲の外にあるからである
(awk
のif-else
文についての詳しい情報は
セクション The if
-else
Statementを参照。
`!='演算子の詳しい情報は
セクション Variable Typing and Comparison Expressionsを参照.)。
既に存在するフィールドに何かを代入することによって
$0
の値は変化するが、たとえフィールドに空文字列を代入したとしても
NF
の値は変化しないということは重要である。
例えば、
$ echo a b c d | awk '{ OFS = ":"; $2 = "" > print $0; print NF }' -| a::c:d -| 4
フィールドは空の値を持って存在しつづけている。 それは、二つのコロンが連続していることで確認できる。
次の例は新しいフィールドを作ったときに何が起こるのかと いうことを見せている。
$ echo a b c d | awk '{ OFS = ":"; $2 = ""; $6 = "new" > print $0; print NF }' -| a::c:d::new -| 6
$5
が間に入って、空の値で作成されていて
(二つめの並んだコロンによって判断できる)、
NF
の値が6に更新されている。
結局のところ、NF
の値を減じることによって新たなNF
の
値より後ろのフィールドの値は失われることとなり、$0
の再計
算が行なわれる。以下はその例である。
$ echo a b c d e f | ../gawk '{ print "NF =", NF; > NF = 3; print $0 }' -| NF = 6 -| a b c
このセクションは長く、awk
における基本的な操作の一つを
説明する。
フィールドセパレータはある一文字か、あるいは正規表現であり、
awk
が入力レコードをどのようにフィールドに分割するかを
制御する。awk
は入力レコード中に
そのセパレータにマッチするキャラクタの並びがないか
検索する。フィールドそのものはマッチしたものの間にあるテキストである
次に挙げる例では出力中のスペースを"*"という シンボルを使って表わしている。
フィールドセパレータが`oo'で、以下の行があったとすると
moo goo gai pan
これは三つのフィールド `m'、 `*g'、`*gai*pan' に分割される。 第二フィールドと第三フィールドの先頭にあるスペースに注意。
フィールドセパレータは組込み変数のFS
で示される。
シェルプログラマは注意すること!
awk
は
(Bourne シェル、sh
やGNU Bourne-Again Shell、Bashのような)
POSIX互換のシェルが使っているIFS
という名前の変数は
使わない。
awk
プログラム中で代入演算子`='
(セクション 代入式を参照)
を使ってFS
の値を変更することができる。
ほとんどの場合、これ(FS
の値を変更すること)を行うのに正しいタイミ
ングは実行の開始時、入力が処理される以前である。これは一番最初のレコード
が適切なセパレータで読み込みが行われるからである。このタイミングで変更を
行うためにはBEGIN
スペシャルパターンを使う
(セクション 特殊パターンBEGIN
とEND
を参照)。
例えば、次の例ではFS
の値を","
という文字列に設定している。
awk 'BEGIN { FS = "," } ; { print $2 }'
Given the input line, 入力として次のものを与える。
John Q. Smith, 29 Oak St., Walamazoo, MI 42139
先のawk
プログラムは
`*29*Oak*St.'
という文字列をレコードから取り出し、出力する。
ときとして、セパレートキャラクタを含んではいるがそれを分割したくはないとい うデータを扱いたいときがあるかもしれない。例えば、`John Q. Smith, LXIX' の様にタイトルがついているとか、拡張子がくっついているようなデータを扱うよう な場合である。入力データの中に次のような名前があったとすると、
John Q. Smith, LXIX, 29 Oak St., Walamazoo, MI 42139
先のサンプルプログラムは` 29 Oak St.'ではなく、` LXIX'を取り出 す。もしプログラムが住所を出力すると期待していたならば、その結果に驚かされ るかもしれない。だから、この様な問題を避けるためにデータのレイアウトと、セパ レータとなるキャラクタは慎重に選択せねばならない。
既に知っているように、通常は
フィールドは一つの空白ではなく空白の並び(スペースかタブか改行)で区切られる。
フィールドセパレータのデフォルトの値は
" "
という文字列であるが、もしこの値を普通通り使ったとしたら、二つ
の空白からなるような行は空白は二つのフィールドセパレータとして扱われ、結果と
して空のフィールドを作りだすのだろうか? 実際には以下の理由によってこのような
ことは行われない。FS
の値が一つの空白であった場合には特殊な場合として、
フィールドの分割のルールをデフォルトと同じ様にする。
FS
の値が他のキャラクタが一文字、例えば","
の様な場合にはキャラ
クタは二つのフィールドを分割する様な結果となる。二つの連続したキャラクタは
空のフィールドを分割する。もし、行の先頭や末尾に(区切りの)キャラクタがあっ
た場合には空のフィールドがあるように区切る。スペースはこのルールに従わない
ただ一つのキャラクタである。
The previous
先の
subsection
サブセクション
でFS
の値として
単一のキャラクタや単純な文字列を使用した。
より一般的には、FS
の値は
なんらかの正規表現であってよい。
この場合、レコード中でそれにマッチ正規表現が
フィールドの分割をする。
例えば
FS = ", \t"
このような代入は for the complete list of similar escape sequences.) カンマ、スペース、タブが後ろに続いている領域が フィールドになる (`\t'はエスケープシーケンスであり、これは タブを表わしている。同じようなエスケープシーケンスの 完全なリストはセクション エスケープシーケンスを参照にある)。
正規表現のあまり重要でない例として、
先の例のカンマのように、一つのスペースでフィールド分割を
行いたいという状況を考えよう。
これは、FS
に"[ ]"
(左ブラケット
スペース、右ブラケット)をセットすることでできる。
この正規表現は一つのスペースにマッチし、他のものには
マッチしない(セクション 正規表現を参照)。
`FS = " "'(一つのスペース)と、`FS = "[ \t\n]+"'
(左ブラケット、スペース、バックスラッシュ、"t"、
バックスラッシュ、"n"、右ブラケット
で、一つ以上のスペース、タブにマッチする正規表現)の間の違いは
重要である。これら両方のFS
の値は、共に
フィールド分割をスペース、タブ、改行(の連続)で行う。
しかし、FS
が" "
であったとき、awk
は
最初に、先頭や末尾で連続している空白のはぎ取りを行い、
それからフィールド分割を実行する。
例を挙げると、次のパイプラインでは`b'が出力される、
$ echo ' a b c d ' | awk '{ print $2 }' -| b
しかし、次のパイプラインでは`a'が出力される (それぞれの文字の回りにはスペースが付いていないことに注意)。
$ echo ' a b c d ' | awk 'BEGIN { FS = "[ \t]+" } > { print $2 }' -| a
この場合、第一フィールドは空(null、empty)である。
先頭や末尾にある空白は$0
が再構成されたときには
いつでも取り除かれる。
例えば次のパイプラインで、
$ echo ' a b c d' | awk '{ print; $2 = $2; print }' -| a b c d -| a b c d
最初のprint
文は読み込んだレコードをレコードの先頭にある空白も含め
てそのまま出力する。$2
に対する代入は、$1
から$NF
まで
をOFS
の値で分割されたものとして連結して$0
を再構成する。先
頭の空白は$1
に注目したときには無視されているので、新しい$0
には含まれない。そして、最後のprint
文は新しい$0
を出力する。
レコード中の個々のキャラクタを別個にチェックしたいと思うことはままあるだ
ろう。gawk
では、それを簡単に行える。単に空文字列(""
)を
FS
にセットするに代入すればよい。この場合、レコード中の個々の独立し
たキャラクタがそれぞれ一つのフィールドとなる。例を挙げると、
$ echo a b | gawk 'BEGIN { FS = "" } > { > for (i = 1; i <= NF; i = i + 1) > print "フィールド", i, "は", $i > }' -| Field 1 is a -| Field 2 is -| Field 3 is b
この出力は以下のようになる。
フィールド 1 は a フィールド 2 は フィールド 3 は b
伝統的に、FS
が""
にセットされているときの振る舞いは
未定義だった。この場合UNIXのawk
は、単にレコード全体を
一つのフィールドであるように扱う(d.c.)。
gawk
は互換モード(セクション コマンドラインオプションを参照)では、
FS
が空文字列の場合にはこのような動作をするようになる。
FS
の設定をする
FS
はコマンドライン上でセットすることができる。
それには`-F'オプションを使用する。例を挙げよう。
awk -F, 'program' input-files
この例ではFS
を`,'というキャラクタにセットしている。このオプ
ションが大文字の`F'を使っていることに注意。これに似た`-f'とい
うオプションは、awk
プログラムが入っているファイルを指定するもので
ある。コマンドラインオプションの大小文字は区別されるので、`-F'と
`-f'という二つのオプションはまったく別のものである。これら両方のオプ
ションを同時に使って、変数FS
に(セパレータを)セットするのと
同時にawk
プログラムをファイルから得ることができる。
`-F'の引数として使った値は、組込み変数FS
に代入したときとまっ
たく同じように扱われる。どういうことかというと、もしフィールドセパレータ
がスペシャルキャラクタを含んでいたら、それを適切にエスケープしなければな
らないということである。例えば、`\'をフィールドセパレータとして使お
うとするなら、次のようにタイプする必要がある。
# FS = "\\" と同じ awk -F\\\\ '...' files ...
`\'はシェルでクォートされるので、awk
がみるのは`-F\\'に
なる。そして、awk
はエスケープキャラクタ(セクション エスケープシーケンスを参照)
の処理を`\\'に対して行うので、最終的に得られるのは一つの`\'で
あり、これがフィールドセパレータとして使用される。
特殊なケースとして、互換モード(セクション コマンドラインオプションを参照),
の場合、`-F'の引数が`t'であったときにはFS
にはタブキャラ
クタがセットされる。これは、`-F\t'とシェルのコマンドラインで指定し
た場合、クォートが取れるので、`\'が削除されてしまう。そこで、
awk
は`t'ではなく、ユーザーが本当に望んだタブによるフィールド
分割をするのである。もし本当に`t'でフィールド分割を行いたいのならば、
コマンドラインで`-v FS="t"' のようにする
(セクション コマンドラインオプションを参照)。
さて、ここで/300/
というパターンと、そのアクションに`print $1'
を含む`baud.awk'というファイル名のawk
プログラムを
使ってみよう。プログラムはこう。
/300/ { print $1 }
FS
に`-'をセットし、`BBS-list'ファイルに
対して使ってみよう。以下のコマンドは、300ボーで運営されている
BBSの名前とその電話番号の最初の三桁を出力する。
$ awk -F- -f baud.awk BBS-list -| aardvark 555 -| alpo -| barfly 555 ...
出力の二行目に注意。元のファイル (セクション 例で使用するデータファイルを参照), の二行目はこうだった。
alpo-net 555-3412 2400/1200/300 A
期待している電話番号中にある`-'ではなく、システム名の一部で ある`-'がフィールドセパレータとして扱われている。 これはあなたがレコードセパレータやフィールドセパレータを 選択するときには、注意しなければならないということの実例である。
多くのUNIXシステムでは、ユーザーはシステムのパスワードファイル中にユーザ ー一人当り一行のエントリをそれぞれ所有している。そこにある情報はコロンに よって区切られていて、最初のフィールドにはユーザーのログオン名が、二番目 のフィールドにはそのユーザーの暗号化されたパスワードがある。パスワードフ ァイルのエントリはおおむね次のようなものである。
arnold:xyzzy:2076:10:Arnold Robbins:/home/arnold:/bin/sh
次のプログラムはシステムのパスワードファイルを検索して、パスワードが設定 されていないエントリを出力する。
awk -F: '$2 == ""' /etc/passwd
POSIXの標準に従えば、awk
はレコードを読み込んだときにフィールドの
分割をするように動作することになっている。これはレコードの読み込みが終わ
った後でFS
の値を変更できるということであり、フィールドの値(どのよ
うに分割されたか、ということ)は新しいものではなく、古い変更前のFS
の値を反映したものであるべきであるということである。
しかし、多くのawk
処理系でこの動作をしておらず、実際にフィールドが
参照されるまで分割を遅らせている。この場合、フィールドは新しい
FS
の値を使って分割されるのだ!(d.c.)この振る舞いは調べるのが難しい。
以下の例はこれら二つのやり方の違いを説明するものである(このsed
(7)コマンドは`/etc/passwd'の最初の一行
だけを出力する)。
sed 1q /etc/passwd | awk '{ FS = ":" ; print $1 }'
これは
root
間違った実装のawk
では大概こうなるだろう。
一方gawk
は次のように出力するだろう。
root:nSijPlPhZZwgE:0:0:Root:/:
以下に、FS
の値によってフィールド分割がどのように
行われるかを簡単にまとめた(`=='は"等しい"ことを
示す)。
FS == " "
FS == any other single character
FS == regexp
FS == ""
(このセクションでは、先進的な、実験的機能について説明している。もしあな
たがawk
初心者なら、初めて読んだときにはここを飛ばしたくなるかもし
れない)
gawk
2.13 で新たに、幅が固定されていてセパレータを持っていないフ
ィールドを扱うための機能を導入した。そこで想定されているデータは典型的に
は古いFORTRANプログラムに対する数値の入力として、あるいは他のプログラム
の入力として扱われることを考慮していないプログラムの出力の二つのうちのい
ずれかだろう。
後で挙げる例は、全てのカラムが可変数のスペースを使用して整列されていて、
空のフィールドがスペースで構成されているデータのテーブルである。現実に、
awk
の通常のFS
に基づいて行うフィールド分割はこの様なケース
ではうまく働かない。awk
プログラム中で$0
に対して
substr
をくり返し呼ぶことで実現できるが
(セクション Built-in Functions for String Manipulationを参照)、
これはポータブルであっても、 awkward であり、大きなフィールド番号では非
効率的なやり方である。
入力レコードを固定幅のフィールドに分割するときには、組み込み変数FIELDWIDTHS
に設定されたスペースで区切られた数字の並びで指定する。並びのそれぞれの数字
は、フィールドの幅をフィールドの間のカラムを含めて指定している。もし、フィー
ルドの間のカラムを無視したいのであれば、後続を無視するようなフィールドの分割
をする幅を指定することもできる。
次のデータはw
ユーティリティの出力である。これはまたFIELDWIDTHS
の使い方を説明するのにちょうどよい。
10:06pm up 21 days, 14:04, 23 users User tty login idle JCPU PCPU what hzuo ttyV0 8:58pm 9 5 vi p24.tex hzang ttyV3 6:37pm 50 -csh eklye ttyV5 9:53pm 7 1 em thes.tex dportein ttyV6 8:17pm 1:47 -csh gierd ttyD3 10:00pm 1 elm dave ttyD4 9:47pm 4 4 w brent ttyp0 26Jun91 4:46 26:46 4:41 bash dave ttyq4 26Jun9115days 46 46 wnewmail
次のプログラムは先ほどのデータを入力とし、アイドルタイムを秒に変換して入
力データの最初の二つのフィールドと、計算した時間を出力する(このプログラ
ムはまだ説明されていないawk
の機能をいくつか使っている)。
BEGIN { FIELDWIDTHS = "9 6 10 6 7 7 35" } NR > 2 { idle = $4 sub(/^ */, "", idle) # 先頭の連続するスペースをはぎとる if (idle == "") idle = 0 if (idle ~ /:/) { split(idle, t, ":") idle = t[1] * 60 + t[2] } if (idle ~ /days/) idle *= 24 * 60 * 60 print $1, $2, idle }
プログラムの実行結果は次の通り。
hzuo ttyV0 0 hzang ttyV3 50 eklye ttyV5 0 dportein ttyV6 107 gierd ttyD3 1 dave ttyD4 0 brent ttyp0 286 dave ttyq4 1296000
別の(より現実的であろう)固定幅の例となる入力データは、投票用紙の山からの
入力だろう。合衆国の一部では、投票者は投票をコンピュータカードのパンチされた
穴を選択することで行う。これらのカードは投票がどの候補者に行われたか、ある
いはどの欄が選択されたかを数えるのに使われる。投票者が一部の投票を行わなかっ
た場合、対応するカードのカラムは空である。awk
プログラムはその様なデー
タを処理するために、 FIELDWIDTHS
機能を使ってデータを単純に読むこと
ができる(もちろん、カードリーダー付きのシステムでgawk
を動かすように
するのは別の話だ!)。
ある値をFS
に代入すると、gawk
はフィールド分割をFS
を
使って行うように戻る。これを`FS=FS'を使ってやれば、FS
の値が
何であるかを知ることなしにできる。
この機能は未だ実験的なものであり、改良されることだろう。gawk
は
FIELDWIDTH
に設定されている内容を使うときにその値が正しいものであ
るかどうかのチェックをしないということにとくに注意すること。
一部のデータベースでは、一つの情報のエントリを一行で保持できない。 この様な場合に、複数行レコードを使うことができる。
そういうデータを扱うための最初のステップは扱うデータのフォーマットの選択で ある。レコードが一行であると定義されないときにどのようにレコードを決定する か? どうやってレコードの分割を行うか?
一つの手段としてはレコードを分割するのに、あまり使われないようなキャラクタか
文字列を使う。ということがある。例えばフォームフィードのコード (awk
で
はCのように、\f
と記述する)をファイルをページを一つのレコードとして分割
するために使うことができるだろう。そうするには変数RS
に"\f"
を
セットすればよい。他の、レコード中のデータの一部分として同じ程度にあまり使わ
れていないキャラクタを使うこともできるだろう。
他のやり方としては空行でレコードを分割するというものがある。
他のやり方としては空行でレコードを分割するという手段がある。特別な場合とし
て、空文字列をRS
にセットすると、それはレコードの区切りとして一行以上
の空行をでレコードを分割するという事を示す。もし、RS
に空文字列をセッ
トすると、レコードは常に最初の空行に出会ったところで終わる。そして、次のレ
コードは空行でない行がきたところでスタートする。どれだけ空行が続いているの
かは関係なく、単に一つのレコードセパレータとして認識される(ファイルの終端
はレコードセパレータとして認識される)。
`RS = ""' としたときと同じ効果を"\n\n+"
をRS
にセットす
ることによって得ることができる。この正規表現はレコードの終端にある改行に
マッチし、一行以上の空白行がレコードの後に続く。それに加えて、正規表現は
常にマッチする最長のパターンを選択する
(セクション How Much Text Matches?を参照)。
したがって、次のレコードは空行でない行がくるまでスタートしない。どれだけ
空行が続いてあるかは関係なく、単に一つのレコードセパレータとして認識され
る。
`RS = ""'と`RS = "\n\n+"'には重要な違いがある。最初のものは、 入力データファイル中の先頭にある改行は無視され、最後のレコードの後に空行 がない場合には、最後の改行がレコードから剥ぎ取られる。二番目のものでは、 このような特殊な処理は行われない(d.c.)。
ここまでで入力がレコードに分割された。第二のステップはレコード中のフィー
ルドをどう分割するか、という事である。フィールドを分割する別のやり方とし
ては、通常の方法で各行をフィールドに分割するというものである。これは特殊
機能の結果、デフォルトで生じることである。RS
に空文字列が設定され
ている場合、改行は常にフィールドセパレータとして動作する。これは、
FS
に設定されている値によるフィールド分割に加えられる形で行われる。
この特別な例外を取り入れることになった元々の動機は、デフォルトの場合に使い易
い動作をするようにするためである (i.e., FS == " "
)。この機能は、
ユーザーが本当は改行をフィールド分割のためのキャラクタとして使いたくないときに、
それを防ぐ手段がないので問題となる。しかしながら、 split
関数を使って
手動で分割を行なうことで望む結果を得ることができる
(セクション Built-in Functions for String Manipulationを参照).
もう一つの方法は一行を一つのフィールドとして出力する。というものがある。
それを実行するには変数FS
に "\n"
という文字列をセットすれば良い
(これは、改行にマッチする単純な正規表現である)。
このやり方で作成されたデータファイルの現実的なサンプルはメイリングリスト だろう。それぞれのエントリは空行で分割される。もし`addresses'という ファイル名のメイリングリストを持っていたら、こんな感じだろう。
Jane Doe 123 Main Street Anywhere, SE 12345-6789 John Smith 456 Tree-lined Avenue Smallville, MW 98765-4321 ...
このファイルを処理する簡単なプログラムはこういったものだろう
# addrs.awk -- 簡単なメイリングリストプログラム # レコードは空行によって区切られる。 # 各行は一つのフィールドとなる。 BEGIN { RS = "" ; FS = "\n" } { print "Name is:", $1 print "Address is:", $2 print "City and State are:", $3 print "" }
このプログラムを実行した結果は次のようになる。
$ awk -f addrs.awk addresses -| Name is: Jane Doe -| Address is: 123 Main Street -| City and State are: Anywhere, SE 12345-6789 -| -| Name is: John Smith -| Address is: 456 Tree-lined Avenue -| City and State are: Smallville, MW 98765-4321 -| ...
アドレスリストを扱うより本格的なプログラムは セクション 郵便の宛て名を出力するを参照. にある。
以下に挙げる例は、RS
の値によってレコードがどのように
分割されるかをまとめたものである(`=='は"'等しい'ということである)。
RS == "\n"
RS == 任意の一文字
RS == ""
FS
に設定されているものに加えて、常にフィールドセパレータとなる、
ファイルの先頭や末尾にある改行は無視される。
RS == regexp
regexpにマッチした文字列によってレコードが分割される。 先頭や末尾にあるregexpは空レコードを区切るとみなされる。
すべてのケースにおいて、gawk
はRT
に
RS
で指定されたものにマッチしたテキストをセットする。
getline
を使った入力
これまで私達はawk
の主入力ストリームから入力データを得ていた。それ
は標準入力(通常はターミナル、ときとして他のプログラムの出力)やコマンドラ
インで指定したファイルであった。awk
言語はgetline
と呼ばれる
特殊な組込みコマンドを持っており、これは明確に制御された元での入力読み取
りに使うことができる。
getline
このコマンドは非常に複雑であり、初心者はあまり使うべきではない。この章が入
力に関してのものであるのでここで記述している。以下に挙げる例はまだ説明され
ていないものが含まれている getline
コマンドの説明である。従って、この
マニュアルの残りの部分を読んでawk
がどのように動作するかの知識を十分
得た後に、ここに戻ってgetline
コマンドを学ぶのが良いだろう。
getline
はレコードが見付かれば1を返し、ファイルの終端に達したときには
0を返す。もし、レコードの読み込みでなんらかのエラーが発生したり、
ファイルがオープンできなかったりした場合にはgetline
は-1を
返す。このときgawk
はERRNO
という変数にエラーが発生したことを
表わす文字列をセットする。
次の例では、command はシェルコマンドを表す文字列である。
getline
を使う
getline
コマンドはカレント入力ファイルから入力するときには引数を省略
することができる。この場合、入力レコードはフィールドに分割される。この
getline
の使い方は、カレント入力レコードに対する処理は終わったが、何
か特別な処理を次のレコードに対してその時点で処理を行いたい、というときに便
利である。たとえば
awk '{ if ((t = index($0, "/*")) != 0) { # tが1だったら、この値は""になる tmp = substr($0, 1, t - 1) u = index(substr($0, t + 2), "*/") while (u == 0) { if (getline <= 0) { m = "unexpected EOF or error" m = (m ": " ERRNO) print m > "/dev/stderr" exit } t = -1 u = index($0, "*/") } # */がファイルの終端にあるとsubstrは""になる $0 = tmp substr($0, t + u + 3) } print $0 }'
このawk
プログラムは入力から`/* ... */'というCスタイルのコメ
ントを取り除く。`print $0'を他の文に置き換えることによって、より複雑な
処理をコメントアウトされた入力に対して、ある正規表現にマッチするかどうかを探
すなどして行うことができる。(このプログラムには隠された問題点がある---それは
一つのコメント終了と開始とが同じ行にあるときにうまく働かない
ということである。
この形式のgetline
コマンドはNF
(フィールドの数。セクション フィールドの検査を参照)、
NR
(それまでに読んだレコードの数。
セクション 入力をレコードへと分割をするやりかたを参照)、
FNR
(現在の入力ファイルから読み込んだレコードの数)、
$0
に新しい値をセットする。
ノート: 新しい$0
の値は、後に続いてあるルールのパターン部とテ
ストするために使われる。このとき、元々の$0
の値は失われる。対照的に
next
文で新しいレコードを読んだときは、即座に通常通りの処理を始め、プ
ログラムの最初のルールから処理を始める(d.c.)。
セクション The next
Statementを参照.
`getline var' を使って、
awk
の入力からの次のレコードを変数varに
読み込むことができる。他の処理は行われない。
例えば、次の行がコメントであるとか特殊な文字列であって、
何のルールも実行することなしにそれを読みたいようなことを想定しよう。
この形式のgetline
は、その様な行を読み込みと
変数への格納を
awk
の行読み込み--ルールのチェックというループが
チェックすることなしにできるのである。
次の例は、入力された二行毎にその行を入れ替える。というものである。入力として
wan tew free phore
というものがあると、出力は
tew wan phore free
こうなる。以下はプログラムである。
awk '{ if ((getline tmp) > 0) { print tmp print $0 } else print $0 }'
ここではgetline
関数は変数NR
and FNR
に(もちろん
varにも)セットする目的で使われている。レコードはフィールドに分割はさ
れず、したがってフィールドの値($0
も含めて)と、NF
の値は変更
されない。
getline
を使ったファイルからの入力`getline < file'を使って、 次のレコードをfileというファイルから読み込むことができる。 このfileはファイル名として特定できる文字列の値を持った式である。 `< file'は異なった場所から直接入力を行うので リダイレクト と呼ばれる。
例えば、次のプログラムはレコードの入力を、カレント入力からのレコードの最初の フィールドが10であったときに、`secondary.input'というファイルから行う。
awk '{ if ($1 == 10) { getline < "secondary.input" print } else print }'
通常の入力ストリームを使わないので、NR
とFNR
の値は変更されない。
しかし、読み込まれたレコードは通常のルールに従ってフィールドに分割され、
$0
および、その他のフィールドの値は変更される。もちろんNF
の値
もその例外ではない。
POSIXに従えば、`getline < expression' は
expression中に`$'以外の演算子が括弧に囲まれないで
現れたときには曖昧となる。たとえば`getline < dir "/" file'
は連接演算子が括弧に囲まれずに現れており、曖昧になっている。
そしてこれは、他のawk
処理系でも問題なく使えるように
したいのなら、`getline < (dir "/" file)' と記述すべきである。
getline
を使ってファイルから変数へ格納`getline var < file'を使って ファイルfileから読み込み、変数varに それをセットする、先に述べたように、 fileは読み込みに使用するファイルを指定する文字列式である。
この使い方のgetline
はどの組み込み変数も変更せず、レコードのフィール
ドへの分解も行われない。ただ単にvarだけが変わる。
例えば、次のプログラムでは入力ファイルを出力するが、入力中に `@include filename'と書かれたレコードがあったときに、その様 なレコードをfilenameというファイルの内容で置き換える。
awk '{ if (NF == 2 && $1 == "@include") { while ((getline line < $2) > 0) print line close($2) } else print }'
これは特別な入力ファイルの名前がプログラム中に書かれていない例である。この 例は、`@include'がある行の二番目のフィールドで指定されるファイルから データを持ってくる。
close
関数は入力中にある`@include'で指定されるファイルの中に同
じものが二度以上指定されているものがあったとしてもきちんと動作することを保
証するために呼ばれている。
セクション Closing Input and Output Files and Pipesを参照.
このプログラムで一つ欠けているのは、本当のマクロプロセッサではできるような ネストした`@include'文(インクルードファイルの中にある `@include')を処理できないという点である。 ネストした`@include'文を処理できるプログラムは セクション ライブラリを使う簡単な方法を参照。
getline
を使ったパイプからの入力
コマンドの出力をパイプを通じてgetline
を
`command | getline'のように使って受けることができる。パイプは
単にあるプログラムの出力を他の入力にリンクするだけである。このケースでは、
command という文字列はシェルコマンドとして実行され、その出力がパイプ
を通じてawk
の入力とされる。この例のgetline
ではパイプからレコー
ドを読み込んでいる。
例えば次のプログラムは入力を出力にコピーするが、ある行が`@execute' で 始まっていると、その行を`@execute'に続く部分をシェルのコマンドとして 実行して、入力をその結果に置き換える。
awk '{ if ($1 == "@execute") { tmp = substr($0, 10) while ((tmp | getline) > 0) print close(tmp) } else print }'
close
関数は入力中の`@execute'のある行で指定されるコマンドに二
つ以上の同一のコマンドが指定されたときに、一回毎にそのコマンドを実行する様
にさせるために呼び出される。
セクション Closing Input and Output Files and Pipesを参照.
入力として以下のものを与える。
foo bar baz @execute who bletch
プログラムの出力はこうなる。
foo bar baz arnold ttyv0 Jul 13 14:22 miriam ttyp0 Jul 13 14:23 (murphy:0) bill ttyp1 Jul 13 14:23 (murphy:0) bletch
このプログラムはwho
コマンドを実行し、結果を出力する(もしこのプロ
グラムがやっていることを自分でやろうとしたら、その結果は異なったものとなる
だろう。誰がシステムにログインしているかで変わるからだ)。
この形式のgetline
では、レコードをフィールドに分割し、NF
をセット
して$0
を再計算する。このとき、NR
とFNR
は変更しない。
POSIXに従えば、`getline | expression' は
expression中に`$'以外の演算子が括弧に囲まれないで
現れたときには曖昧となる。たとえば`"echo " "date" | getline'
は連接演算子が括弧に囲まれずに現れており、曖昧になっている。
そしてこれは、他のawk
処理系でも問題なく使えるように
したいのなら、`("echo " "date") | getline' と記述すべきである
(gawk
はこの件に関しては正しく動作するのだが、それに依存すべき
ではない。いずれにしろかっこによって読みやすいものになるのだ)。
getline
を使ってパイプからの内容を変数に格納する
`command | getline var'を使ったとき、
commandの出力は
パイプを通じてgetline
に渡り、さらに変数var
に格納される。例えば次に挙げるプログラムでは date
ユーティリティを使用
して日付と時刻を読み込み、それをcurrent_time
という変数に格納し、さら
に表示している。
awk 'BEGIN { "date" | getline current_time close("date") print "Report printed on " current_time }'
この使い方のgetline
では、どの組み込み変数も変更されず、レコードの
フィールド分割も行われない。
getline
のまとめ
これらすべてのgetline
において、$0
やNF
が更新されて
も、(読み込まれた)レコードはawk
プログラムのすべてのパターンでテス
トが行われるということはなく、awk
がメイン処理ループで通常通りレコ
ードを読み込んだかのように動作する。しかし新しいレコードは残りのルールに
対してはテストが行われる。
先に述べたように
(セクション 各種getline
のまとめを参照)、
多くの
awk
処理系では、一つのawk
プログラムで開くことのできる
パイプの数はたったの一つである! gawk
ではこのような制限はなく、使
用しているオペレーティングシステムが許す限りのパイプをオープンすることが
できる。
getlineを、BEGIN
ルールの中でリダイレクトをせずに使った場合、
興味深い副作用が発生する。リダイレクトされていないgetline
は
コマンドラインで指定されたデータファイルから読み込むので、最初のgetlile
はawk
にFILENAME
の値をセットさせる。通常、
(BEGINルール
では)コマンドラインで指定されたデータファイルを
処理しないのでFILENAME
はBEGIN
ルールでは値を持たない(d.c.)。
(セクション 特殊パターンBEGIN
とEND
を参照と
セクション 情報を伝達する組込み変数を参照.)
以下に六種類のgetline
の使い方とそれぞれの使い方でセットされる組込み
変数を一覧にした。
getline
$0
, NF
, FNR
, NR
をセットする。
getline var
FNR
, NR
をセットする。
getline < file
$0
, NF
をセットする。
getline var < file
command | getline
$0
, NF
をセットする。
command | getline var