移動先 先頭, , , 末尾 セクション, 目次.

入力ファイルの読み込み

典型的なawkプログラムでは、すべての入力は標準入力(デフォルトでは キーボードだが、ほとんどの場合は他のコマンドからのパイプ)かコマンドライ ンで指定した名前のファイルのどちらかから読み込まれる。入力ファイルを指定 している場合、awkは指定した順番に従ってすべてのファイルからデータ の読み込みを行う。ある時点で処理している入力ファイルのファイル名は組込み 変数FILENAME(セクション 組み込み変数を参照)から得ることができる。

入力はレコードと呼ばれる単位で読み込まれ、プログラムに記述されたル ールに従い、一度に一つのレコードを処理する。デフォルトでは各レコードはひ とつの行である。各レコードはフィールドと呼ばれる塊に自動的に分割さ れる。これはあるレコードの一部分について作業を行うプログラムに便利である。

まれに特別な場合に、getlineコマンドを使う必要にせまられることがあ るだろう。getlineコマンドは任意の数のファイルから陽に入力を行うこ とを可能にし、コマンドライン上で名前を指定しなくてもファイルから読み込み ができるので重宝する。 (セクション getlineを使った入力を参照).

入力をレコードへと分割をするやりかた

awkユーティリティはawkプログラムに対する入力をレコードとフ ィールドに分割する。レコードはレコードセパレータと呼ばれるキャラク タによって分割される。デフォルトでは、レコードセパレータは改行キャラクタ である。これはデフォルトではレコードが単一の行であるからである。組込み変 数のRSに値をセットすることによって、レコードセパレータを別のキャ ラクタにすることができる。

他の変数と同じ様に、代入演算子`=' (セクション 代入式を参照)を使ってawkプロ グラム中でRSの値を変更することも可能である。

新しいレコードセパレータのキャラクタは、それが文字列定数であることを示す ために引用符で括ったほうがよい。ほとんどの場合、これを行うための正しいタ イミングは実行開始時、入力がなんらかの処理を行う前、つまり、最初のレコー ドがレコードセパレータを使って読み込まれる前である。これを行うために code{BEGIN}スペシャルパターを使用する (セクション 特殊パターンBEGINENDを参照)。 例えば、

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の値を変更した場合、 その新しい値は後続のレコードの区切りに使用されるが、 現在処理されているレコード(と既に処理されたレコード) にはなんの影響も及ぼさない。

レコードの区切りが終了した後で、awkRSにマッチした入力中のテキストを 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'を出力す るとその結果には新しいフィールドが含まれている。

この再計算の効果と再計算を行う機能についての説明はまだされていない。フィー ルドの区切りを指定する出力フィールド指定子OFSNF (フィール ドの数セクション フィールドの検査を参照)についての説明もまだである。例 えば、NFの値は新たに作り出されたフィールドのフィールド番号の最大のも のがセットされる。

しかし気をつけなければならないのは、範囲外のフィールドにたいする 参照$0の値もNFの値も変更しない ということである。 範囲外のフィールドに対する参照は単に空文字列を生成するだけである。 例を挙げよう。

if ($(NF+1) != "")
    print "can't happen"
else
    print "everything is normal"

これは`everything is normal'が出力されるはずだ。 なぜなら、NF+1は範囲の外にあるからである (awkif-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スペシャルパターンを使う (セクション 特殊パターンBEGINENDを参照)。 例えば、次の例では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
フィールドはregexpにマッチした文字列によって 分割される。先頭、および末尾で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の値が 何であるかを知ることなしにできる。

この機能は未だ実験的なものであり、改良されることだろう。gawkFIELDWIDTHに設定されている内容を使うときにその値が正しいものであ るかどうかのチェックをしないということにとくに注意すること。

複数行レコード

一部のデータベースでは、一つの情報のエントリを一行で保持できない。 この様な場合に、複数行レコードを使うことができる。

そういうデータを扱うための最初のステップは扱うデータのフォーマットの選択で ある。レコードが一行であると定義されないときにどのようにレコードを決定する か? どうやってレコードの分割を行うか?

一つの手段としてはレコードを分割するのに、あまり使われないようなキャラクタか 文字列を使う。ということがある。例えばフォームフィードのコード (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"
レコードは改行キャラクタ(`\n')で分割される。 言い換えると、データファイルの各行が空行も含めて、 独立したレコードとなる。
RS == 任意の一文字
レコードは指定されたキャラクタにより分割される。 このキャラクタが連続している場合には、空のレコードを区切っていると みなされる。
RS == ""
空行の連続によってレコードを分割する。改行は FSに設定されているものに加えて、常にフィールドセパレータとなる、 ファイルの先頭や末尾にある改行は無視される。
RS == regexp

regexpにマッチした文字列によってレコードが分割される。 先頭や末尾にあるregexpは空レコードを区切るとみなされる。

すべてのケースにおいて、gawkRTRSで指定されたものにマッチしたテキストをセットする。

getlineを使った入力

これまで私達はawkの主入力ストリームから入力データを得ていた。それ は標準入力(通常はターミナル、ときとして他のプログラムの出力)やコマンドラ インで指定したファイルであった。awk言語はgetlineと呼ばれる 特殊な組込みコマンドを持っており、これは明確に制御された元での入力読み取 りに使うことができる。

Introduction to getline

このコマンドは非常に複雑であり、初心者はあまり使うべきではない。この章が入 力に関してのものであるのでここで記述している。以下に挙げる例はまだ説明され ていないものが含まれている getlineコマンドの説明である。従って、この マニュアルの残りの部分を読んでawkがどのように動作するかの知識を十分 得た後に、ここに戻ってgetlineコマンドを学ぶのが良いだろう。

getlineはレコードが見付かれば1を返し、ファイルの終端に達したときには 0を返す。もし、レコードの読み込みでなんらかのエラーが発生したり、 ファイルがオープンできなかったりした場合にはgetlineは-1を 返す。このときgawkERRNOという変数にエラーが発生したことを 表わす文字列をセットする。

次の例では、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の結果を格納する

`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
}'

通常の入力ストリームを使わないので、NRFNRの値は変更されない。 しかし、読み込まれたレコードは通常のルールに従ってフィールドに分割され、 $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を再計算する。このとき、NRFNRは変更しない。

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において、$0NFが更新されて も、(読み込まれた)レコードはawkプログラムのすべてのパターンでテス トが行われるということはなく、awkがメイン処理ループで通常通りレコ ードを読み込んだかのように動作する。しかし新しいレコードは残りのルールに 対してはテストが行われる。

先に述べたように (セクション 各種getline のまとめを参照)、 多くの awk処理系では、一つのawkプログラムで開くことのできる パイプの数はたったの一つである! gawkではこのような制限はなく、使 用しているオペレーティングシステムが許す限りのパイプをオープンすることが できる。

getlineを、BEGINルールの中でリダイレクトをせずに使った場合、 興味深い副作用が発生する。リダイレクトされていないgetlineは コマンドラインで指定されたデータファイルから読み込むので、最初のgetlileawkFILENAMEの値をセットさせる。通常、 (BEGINルールでは)コマンドラインで指定されたデータファイルを 処理しないのでFILENAMEBEGINルールでは値を持たない(d.c.)。 (セクション 特殊パターンBEGINENDを参照と セクション 情報を伝達する組込み変数を参照.)

以下に六種類のgetlineの使い方とそれぞれの使い方でセットされる組込み 変数を一覧にした。

getline
$0, NF, FNR, NRをセットする。
getline var
var, FNR, NRをセットする。
getline < file
$0, NFをセットする。
getline var < file
varをセットする。
command | getline
$0, NFをセットする。
command | getline var
varをセットする。

移動先 先頭, , , 末尾 セクション, 目次.