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

入力ファイルの読み込み

典型的なawkプログラムでは、すべての入力は標準入力(デフォルトではキー ボードだが、多くの場合は他のコマンドからのパイプだろう)か、コマンドライン で指定されたファイルから行われるだろう。もし、入力ファイルを特定したいので あれば awkが順に従って読み込みを行っているので、次のファイルに行く前 にすべてのデータを読み込んでしまう現在処理しているファイルの名前は組み込み 変数のFILENAMEに格納されている(セクション 組込み変数を参照)。

入力はレコードと呼ばれるユニットを単位として読み込まれ、一回にルールで処理 されるものもレコードである。デフォルトでは、レコードは一つの行である。読み 込まれたレコードは自動的にフィールド毎に分割され、ルールがそれぞれの(レコー ドの)ある部分に対して処理しやすいようにする。

まれなケースで、getline コマンドを使って複数のファイルから入力を行う ことを(プログラムに)明記する必要があるかもしれない。 (セクション getlineを使った入力を参照).

入力のレコードへの分割

awk言語はその入力を、レコードとフィールドに分割する。 レコードはレコードセパレータと呼ばれるキャラクタで分割される。 デフォルトではレコードセパレータは改行コードであり、レコードを一行の テキストと定義している。

時として、あなたはレコードの区切りとして違ったキャラクタを使いたくなるだろ う、そういった場合には組み込み変数RSを変更すればよい。 RSの値は レコードの区切りを示す文字列であり、デフォルトでは"\n"、つまり改行が 使われている。それは、一行をデフォルトのレコードとして扱うためである。

RSにはその値として文字列を設定できるが、その文字列の先頭にあるキャラク タだけがレコードセパレータとして使用され、その他のキャラクタは無視される。 他の組み込み変数が設定された値をすべて使うのに対して、 RSだけは例外 である。

RSの値は、awk プログラムの中で代入演算子`='を使って変更 することができる。(セクション 代入式を参照) 新しいレコードセパレータのキャラクタは、文字列定数にするために二重引用符で くくる必要がある。正しいタイミングでこれを行うには、プログラムの実行開始時 の、入力を処理しはじめる以前。つまり一番最初のレコードを読み込むためにセパ レータを使う以前でなければならない。これを行うために、特殊なBEGINと いうパターンを使う (セクション スペシャルパターンBEGINENDを参照)。例えば、

awk 'BEGIN { RS = "/" } ; { print $0 }' BBS-list

入力を読み込む前に、RSの値を"/"を変更している。それは、スラッ シュを先頭に持つ文字列であるから、結果としてスラッシュによってレコードへの 分割が行われる。それから入力ファイルの読み込みが行われ、awk プログラ ムの二番目のルール(アクションを持たないパターン)は各レコードを出力する。 print 文は出力の最後に改行を付加する。つまりこのawkプログラム は入力のスラッシュを改行に変えたものを出力する。

レコードセパレータをコマンドライン上で変更するもう一つの方法は変数への代入 機能を使うことである。 (セクション awkの起動を参照).

awk '{ print $0 }' RS="/" BBS-list

これは`BBS-list'を処理しはじめる前にRS`/'にセットする。

入力ファイルの終端に達した場合は、最後のキャラクタがRSにない キャラクタであってもレコードの入力を終わらせる。

空の文字列""(キャラクタがなにもない文字列)をRSにセットする ことは、レコードのセパレータとして空行を採用する。という特別な意味がある。詳 しくは、セクション 複数行レコードを参照.

awkは現在入力を行っているファイルから読み込みを行ったレコードの数を 保持している。この値はFNRと呼ばれる組み込み変数に格納されている。こ の値は新しいファイルを読み始めるたびに0にリセットされる。別の組み込み変数 NRには、全ての入力ファイルを通じて入力したレコードの数が保持されてい て、実行開始時は0であるが自動的に0にリセットはされはしない。

awkの実行中にRSの値を変更した場合、新しく設定した値は後続のレ コードを区切るのに使用されるが、変更した時点で読み込まれているレコード(と、 既に読み込まれ処理されたレコード)には影響しない。

フィールドの検査

awk が入力レコードを読み込んだときに、インタプリタによって自動的にフィ ールドと呼ばれる単位に分割されたり、解析されたりする。デフォルトでは行中の単 語のように、空白によってフィールドへの分割が行われる。 awk での空白は 一つ以上のスペース、またはタブの並びである。改行や改頁などの他のプログラミン グ言語で空白とみなされるようなキャラクタはawk では空白でないと認識さ れる。

フィールドの目的は、レコードの個々の部分をより参照しやすくする為である。こ のフィールドを使わなければならないという訳ではない(もし望むならば、レコー ド全体を扱うこともできる)が、フィールドは awkプログラムをパワフルな ものにする。

awk プログラムでフィールドを参照するには、ドル記号`$'に続けて参 照したいフィールドの番号を続けて書けばよい。したがって、$1は最初のフィ ールドを、$2は二番目のフィルードを参照するし、他の数字についても同様 である。例えば、入力として次のような行があったとき、

This seems like a pretty nice example.

最初のフィールド$1`This'、二番目のフィールドは$2`seems'…という具合になる。最後のフィールドは$7でその内容は `example.'である。なぜなら、`e'`.'の間にスペースがないの でピリオドは7番目のフィールドに含まれるからである。

たとえフィールドがたくさんあったとしても、レコード中の最後のフィールドは $NFという書き方で得る事ができる。先の例では$NF$7と同 じで`example.'である。もしレコードにフィールドが7個しかないのに $8の様に最後のフィールドを越えて参照すると、結果として空の文字列が返 される。

`$'のついていないNFは、カレントレコード中のフィールドの数を 保持している組み込み変数である。

0番目のフィールドを参照するかのように見える$0は特別で、入力されたレ コードそのものを表現する。これはフィールドで参照したくないような場合に使わ れるだろう。

幾つか例を挙げてみよう。

awk '$1 ~ /foo/ { print $0 }' BBS-list

このサンプルでは`BBS-list'というファイルの中で、`foo'という文字列 を含んでいるレコードをすべて表示する。`~'matching operator (セクション 比較式を参照) と呼ばれる演算子で、文字列(この例では$1フィールド)が与えられた 正規表現とマッチするかどうかをテストするものである。

対照的に次の例では、

awk '/foo/ { print $1, $NF }' BBS-list

各入力レコードに対し、レコード中に`foo'があるものを検索し、それがあっ たレコードの最初と最後のフィールドを出力する。

定数でないフィールド番号

フィールドの番号は定数である必要はない。`$'の後に参照したいフィールド を表す任意の式を書けば良い。式の値がフィールド番号を表すものとして認識され る。値が数値ではなく文字列であれば、それは数値に変換されて扱われる。次の例 を見てみよう。

awk '{ print $NR }'

NR はそれまでに読んだレコードの数を返す。例えば最初のレコードを読ん だときは1、二番目なら2、の様になる。この例では最初のレコードを読んだときには 最初のフィールドを出力し、二番目のレコードの時は二番目のフィールドを出力し、 以下同様に動作する。たとえば20番目のレコードの時ならば20番目のフィールドを 出力する。ここで、レコードに20個未満のフィールドしかないのであれば空行が出 力される。

式としてフィールド番号を使った別の例を挙げる。

awk '{ print $(2*2) }' BBS-list

awk(2*2)という式を評価し、それからその値を出力すべきフィー ルドの番号として使用する。`*'は乗算を表すので、2*2 という式を評 価すると4となる。括弧は`$'のオペレーションより先に掛け算を行わせるため である。フィールド番号を指定する式で二項演算子を使うときは常にこのようにす る必要がある。この例では`BBS-list'というファイル中の全ての行の、運営時 間(4番目のフィールド)を出力する。

フィールド番号が0になるような式を与えると、その結果はレコード全体となる。だ から、 $(2-2)の値は$0と同じである。負の数によるフィールド番号 の指定は許されていない。

カレントレコードのフィールドの数は、組み込み変数の NFに格納されている。 (セクション 組込み変数を参照) NFの式は特別な機能ではなく、NFを評価し たそのままの結果であり、フィールドの番号として使うこともできる。

フィールド構成の変更

あなたはawkプログラムの中で、awkが切り分けたフィールドを変更 することができ、この変更はカレントレコードにも影響する。(これは、入力には 影響しない。つまり、awkは入力ファイルを変更したりはしない)

次の例を見てみよう

awk '{ $3 = $2 - 10; print $2, $3 }' inventory-shipped

`-'は減算の記号で、このプログラムは三番目のフィールド$3$2 - 10の結果、つまり二番目のフィールドから10を引いた結果を 格納している。(セクション 算術演算子を参照.) そのあとで、二番目と三番目のフィールドを出力している。

この作業をするために、フィールド$2は数値として意味のあるテキストでな ければならない。つまり計算するために数値に変換できるような文字列でなければ ならないという事である。引き算の結果は再度文字列に変換されて三番目のフィー ルドに格納される。 セクション 文字列と数値の変換を参照.

フィールドの値を変更したとき、入力レコードは(新しいフィールドが古いフィー ルドにあった場所にあるとして)再度計算される。したがって、$0はフィー ルドが入れかわったことが反映して変更される。だから、

awk '{ $2 = $2 - 10; print $0 }' inventory-shipped

この例では各行の二番目のフィールドが10減らされている入力ファイルのコピーが 出力される。

最大のフィールド番号より大きい、存在しないフィールドに対する代入も可能で ある。例を挙げよう

awk '{ $6 = ($5 + $4 + $3 + $2) ; print $6 }' inventory-shipped

ここでは$6が新たに作り出されて、 $2, $3, $4, $5の各フィールドの合計が格納される。 `+'は足し算の記号であ る。 `inventory-shipped'というファイルを使って各月ごとの積込量の合計を $6に設定する。

新しいフィールドを作成すると、awk内部にあるカレントレコードのコピー が変更される。したがって、フィールドを付け加えた後で`print $0'を出力す るとその結果には新しいフィールドが含まれている。

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

けれども、範囲外のフィールドに対する参照は$0NFのどちらも 変更しない。その様な参照は空文字列を作り出す。たとえば、

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

これは`everything is normal'を出力する。なぜなら、NF+1は確実に 範囲外であるからだ。 (セクション ifを参照にif-else 文の詳しい説明がある)

重要な事は、フィールドに代入を行う事によって$0の値は変更されるが、 NFの値は、フィールドにnullを代入したときでさえ変更されない。というこ とである。例えば次の例では、

echo a b c d | awk '{ OFS = ":"; $2 = "" ; print ; print NF }'

こう表示されるだろう。

a::c:d
4

フィールドとしてはまだ存在しているが、その内容は空である。そのことは、二つ のコロンが連続している部分があることから知ることができる。

フィールド分割の指定

(このセクションは少々長い。ここではawkでの基本的なオペレーションの大 部分が記述されている。もしあなたがawkの初心者だったら、正規表現のセ クションの後でもう一度ここを読む事を勧める。 セクション パターンとしての正規表現を参照.)

awk が行っている入力レコードのフィールドへの分割はfield separator によって制御される。フィールドセパレータは一つのキャラクタか正規表現である。 awkは入力されたレコードをセパレータにマッチするかどうかスキャンする。 たとえばフィールドセパレータが`oo'で次のような入力だったとすると、

moo goo gai pan

これは三つのフィールド`m', ` g' ` gai pan' に分割されるだ ろう。

フィールドセパレータは組み込み変数FSに格納されている。シェルプログラ マーは気をつけて欲しい。awkはシェルが使っている IFSという名前 は使っていないのだ。

FSの値はawkプログラムの中で代入演算子`='を使って変更でき る。(セクション 代入式を参照). 多くの場合、実行を始めた時点、入力がまだなされる以前。つまり最初のレコード に対してセパレータが適用される前に行うのが変更を行う正しいタイミングである。 この為にBEGINスペシャルパターンを使う (セクション スペシャルパターンBEGINENDを参照). 例えば、FSの値とし て","という文字列を設定する。

awk 'BEGIN { FS = "," } ; { print $2 }'

入力として次のデータを与える。

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の値が他のキャラクタが一文字、例えば","の様な場合にはキャラ クタは二つのフィールドを分割する様な結果となる。二つの連続したキャラクタは 空のフィールドを分割する。もし、行の先頭や末尾に(区切りの)キャラクタがあっ た場合には空のフィールドがあるように区切る。スペースはこのルールに従わない ただ一つのキャラクタである。

より一般的にはFS の値は正規表現文字列であるだろう。レコードの中の正 規表現にマッチする部分で(そのレコードは)フィールドに分割される。例えば次 の様な代入文があったとすると、

FS = ", \t"

このセパレータは、カンマとそれに続くスペースとタブからなるエリアで入力された 行をフィールドに分割する(`\t' はタブを表す)。

あまりおもしろくない正規表現の例として、カンマと同じ様にスペース一つでフィー ルドを分割する時のやり方を記述する。それには、FS"[ ]" をセットすればよい。この正規表現は一つのスペースとマッチして、それ以外のものに はマッチしない。

FSはコマンドラインでもセットできる。それには`-F'に続けて次のよ うに書けばよい。

awk -F, 'program' input-files

FS`,'をセットするために、大文字の`F'の引数としている。 これは`-f'awkプログラムがあるファイル名を指示しているのと対照 的である。コマンドオプションでは、文字の大小文字を区別して認識する。だから、 `-F' オプションと `-f'オプションは全然別のものであり、 FS にセットするのと、awk プログラムを指示するのとを同時にオプション指定 することもできる。

`-F'の引数での指定は、組み込み変数FSへの代入と同じ様に処理される。 だから、フィールドセパレータがスペシャルキャラクタを含んでいるのであればそれ はエスケープする必要がある。たとえば、フィールドセパレータとして `\'を 使おうとするならば次のようにタイプしなければならないだろう。

# same as FS = "\\" 
awk -F\\\\ '...' files ...

`\'はシェルの中でクォーティングしているのでawkでは `-F\\' として受け取る。awk`-F\\'をエスケープキャラクタと して処理するので(セクション 定数式を参照)、最終的に単なる一 つの`\'がフィールドセパレータとして使われる。

特殊な場合として、互換モード(セクション awkの起動を参照) では、`-F' の引数として `t'が指定されると、FSにタブキャラ クタをセットする(これは`-F\t'がシェルによって`\'が削除されるの でawk はユーザーが`t'ではなく、タブによってフィールドの分割を 行いたいのだと扱うからである。もし、フィールドセパレータとして`t'を使 いたいのならば、コマンドラインで `-v FS="t"'とすればよい)。

例えば、次のようなパターン/300/を含んでいる行に対して `print $1' というアクションを実行するプログラム(`baud.awk'と呼ぼう)を使ってみよ う。

/300/   { print $1 }

FS`-' をセットして、プログラムに`BBS-list'をデータファ イルとして実行してみよう、次のコマンドは 300ボーで運営されているBBSの名前と、 その電話番号の先頭3桁のリストを表示する。

awk -F- -f baud.awk BBS-list

実行結果はこうなるだろう

aardvark     555
alpo
barfly       555
bites        555
camelot      555
core         555
fooey        555
foot         555
macfoo       555
sdace        555
sabafoo      555

二行目の出力に注目して欲しい。元々のファイルでは二行目はこうだった。

alpo-net     555-3412     2400/1200/300     A

電話番号の中で区切るために使っていた`-'がシステムの名前にある`-' と一致して、フィールドセパレータとして使われてしまったのだ。これは、フィール ドセパレータを選択するときには注意しなければならないということである。

次のプログラムはシステムのパスワードファイルを検索し、パスワードを設定して いないユーザーのエントリを出力する。

awk -F: '$2 == ""' /etc/passwd

ここではフィールドセパレータを設定するのにコマンドラインオプション `-F' を使っている。`/etc/passwd'はコロンによってフィールドに別れていて、二 番目のフィールドにはユーザーのパスワードが記述されている。もしこのフィール ドが空なら、そのユーザーはパスワードを持っていないということである。

POSIX の標準にしたがうと、awkはレコードのフィールドへの分割を読み込 んだときに行う。これは、レコードを読んだ後にFSの値を変更することはで きるが、その変更は変更前に読まれたレコードのフィールドには影響しないという ことである。そのフィールドは変更前のFSの値を反映したものであり、新し い値は関係していない。

しかしながらawk処理系の多くではそうではなく、実際 にフィールドへの参照が発生したときに、その時点での FSの値を使って分 割を行うのである。このような動作は分析するのが難しい。次の例ではこれら二つ の手法の結果を例示する。 (この sed コマンドは`/etc/passwd'の最 初の一行だけを出力する)

sed 1q /etc/passwd | awk '{ FS = ":" ; print $1 }'

これは大概の場合は次のような結果となるだろう

root

これは間違った実装によるawkの元での結果であり、 gawkでは次のような結果を出力するだろう。

root:nSijPlPhZZwgE:0:0:Root:/:

`FS = " "'(ただ一つのスペース)と、`FS = "[ \t]+"'(一つ以上 のスペース、もしくはタブ)の両者には大きな違いがある。どちらの場合も、 FS の値、つまりスペースやタブでフィールドに分割される。しかし、 FSの値が " "の時は、 awkはレコードの先頭や末尾にある連 続した空白を取り去り、それからフィールドを決定する。例えば次の式は`b'を 出力する。

echo ' a b c d ' | awk '{ print $2 }'

しかし次の例では`a'を出力する。

echo ' a b c d ' | awk 'BEGIN { FS = "[ \t]+" } ; { print $2 }'

この例の場合、最初のフィールドは空である。

先頭や末尾にある連続した空白をはぎとるという動作は、$0が再計算 されたときには常におこなわれる。したがって、

echo '   a b c d' | awk '{ print; $2 = $2; print }'

このパイプラインは次のような結果となる。

   a b c d
a b c d

最初のprint文は読んだままのレコードを、行頭の空白を含んだ形で出力する。 $2に対する代入で$0$1 から$NFまでをOFSを 区切りとして連結した結果に再構成される。 $1には行頭の空白はないので、 再構成された$0にもそれはない。最終的に最後の print文では新し い$0が出力される

次にFSの値に基づいてどのようにフィールド分割がなされるかをかいつまん で説明する。

FS == " "
フィールドは空白によって分割され、先頭の空白と末尾の空白は無視される。 これはデフォルトの動作である。
FS == any single character
フィールドは指定されたキャラクタによって分割される。レコードの先頭や末尾など で連続してキャラクタが並んだときには空のフィールドに分割される。
FS == regexp
フィールドはregexpにマッチするキャラクタの並びで分割される。 レコードの先頭、末尾にあるregexpは空のフィールドに分割される。

固定長データの読み込み

(このセクションでは高等な、実験的な機能について述べている。もし、あなたが awkの初心者であれば、初めてこれを読んでいるときにはこのセクションを 飛ばしたほうがいいだろう)

gawk 2.13 では新たに、幅が固定されていてセパレータを持っていないフィ ールドを扱う為の機能を導入している。そこで想定されているデータは典型的には 古いFORTRANプログラムに対する数値の入力として、 あるいは他のプログラムの入力として扱われることを考慮していないプログラムの 出力の二つのうちのいずれかだろう。

後で挙げる例は、全てのカラムが可変数のスペースを使用して整列されていて、空の フィールドがスペースで構成されているデータのテーブルである。現実に、awk の通常のFSに基づいて行うフィールド分割はこの様なケースではうまく働か ない(awkプログラム中でsubstr$0に対してくり返し呼ぶこ とで実現できるが、これはポータブルであっても、 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 機能を使ってデータを単純に読むこと ができる。

この機能はまだ試験中で、これから改良されるだろう。

複数行レコード

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

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

一つの手段としてはレコードを分割するのに、あまり使われないようなキャラクタか 文字列を使う。ということがある。例えばフォームフィードのコード (awkで はCの様に、\fと記述する)を、ファイル中の一ページを一つのレコードとして分割 するために使うことができるだろう。そうするには変数RS"\f"を セットすればよい。他の、レコード中のデータの一部分として同じ程度にあまり使わ れていないキャラクタを使うこともできるだろう。

他のやり方としては空行でレコードを分割するという手段がある。特別な場合とし て、空文字列をRSにセットすると、それはレコードの区切りとして一行以上 の空行をでレコードを分割するという事を示す。もし、RSに空文字列をセッ トすると、レコードは常に最初の空行に出会ったところで終わる。そして、次のレ コードは空行でない行がきたところでスタートする。どれだけ空行が続いているの かは関係なく、単に一つのレコードセパレータとして認識される(ファイルの終端 はレコードセパレータとして認識される)。

第二のステップはレコード中のフィールドをどう分割するか、という事である。 一つの方法は一行を一つのフィールドとして出力する。というものがある。 それを実行するには変数FS"\n"という文字列をセットすれば良い。 (これは、改行にマッチする単純な正規表現である)

フィールドを分割する別のやり方としては、通常の方法で各行をフィールドに分割す るというものである。これは特殊機能の結果、デフォルトで生じることである。 RS に空文字列が設定されている場合、改行は常にフィールドセパレータとし て動作する。これは、FSに設定されている値によるフィールド分割に加えら れる形で行われる。

この特別な例外を取り入れることになった元々の動機は、デフォルトの場合に使い易 い動作をするようにするためである (i.e., FS == " ")。この機能は、 ユーザーが本当は改行をフィールド分割の為のキャラクタとして使いたくないときに、 それを防ぐ手段がないので問題となる。しかしながら、 split関数を使って 手動で分割を行なうことで望む結果を得ることができる (セクション 組込みの文字列操作関数を参照)。

getlineを使った入力

これまでは入力ファイルをawkの主な入力ストリーム、つまり標準入力(多 くの場合は端末)かコマンドラインで指定されたファイルとしてきた。 awkgetlineと呼ばれる特殊な組み込み関数を持っている。この関数は明確な 制御の元に入力を行うのに使われる。

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

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

次の例では、command はシェルコマンドを表す文字列である。

getline
getline コマンドはカレント入力ファイルから入力するときには引数を省略 することができる。この場合、入力レコードはフィールドに分割される。この getline の使い方は、カレント入力レコードに対する処理は終わったが、何 か特別な処理を次のレコードに対してその時点で処理を行いたい、というときに便 利である。たとえば
awk '{
     if (t = index($0, "/*")) {
          if (t > 1)
               tmp = substr($0, 1, t - 1)
          else
               tmp = ""
          u = index(substr($0, t + 2), "*/")
          while (u == 0) {
               getline
               t = -1
               u = index($0, "*/")
          }
          if (u <= length($0) - 2)
               $0 = tmp substr($0, t + u + 3)
          else
               $0 = tmp
     }
     print $0
}'
このawkプログラムは入力から`/* ... */'というCスタイルのコメ ントを取り除く。`print $0'を他の文に置き換えることによって、 コメントアウトされた入力に対してある正規表現にマッチするかどうかを探 すなどして、より複雑な処理を行うことができる (このプログラムには隠された問題点がある。わかる かな?)。 こういった使い方のgetlineNF (フィールドの数、 セクション フィールドの検査を参照)、NR (いままでに読み込んだレコードの 数、 セクション 入力のレコードへの分割を参照)、FNR(現在の 入力ファイルから読み込んだレコードの数)、そして$0をそれぞれセットする。 ノート: 新しい$0の値は、後に続いてあるルールのパターン部とテ ストするために使われる。このとき、元々の$0の値は失われる。対照的に next文で新しいレコードを読んだときは、即座に通常通りの処理を始め、プ ログラムの最初のルールから処理を始める。 セクション nextを参照.
getline var
この書式のgetlineはレコードを変数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と変数FNRに(もちろん varにも)値をセットする目的で使われている。読み込まれたレコードのフィールドへの分割はさ れず、したがってフィールドの値($0 も含めて)と、NFの値は変更 されない。
getline < file
この書式でのgetline 関数はfileというファイルから入力を行う。こ のfileはファイル名として特定できる文字列の値を持った式である。 `< file'は異なった場所から直接入力を行うので redirection と呼ばれる。 この様な書き方は、通常の入力ストリームではなく、ある特定のファイルから入力を したいというときに役に立つ。例えば次のプログラムは、カレン ト入力から読み込んだレコードの最初のフィールドが10であったときに、 レコードの読み込みを`foo.input'というファイルから行う。
awk '{
    if ($1 == 10) {
         getline < "foo.input"
         print
    } else
         print
}'
通常の入力ストリームを使わないので、NRFNRの値は変更されない。 しかし、読み込まれたレコードは通常のルールに従ってフィールドに分割され、 $0 および、その他のフィールドの値は変更される。もちろんNFの値 もその例外ではない。 これはレコードに対して、レコードが通常通りに読み込まれてawkのメイン ループで処理されるときのようにawkプログラムの全てのパターンをテスト してみるという事をされないが、新しいレコードは後続のルールに対しては getlineをリダイレクトなしに使ったときのようにテストが行われる。
getline var < file
この書式のgetline関数はその入力を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'で指定されるファイルの中に同 じものが二度以上指定されているものがあったとしてもきちんと動作することを保 証するために呼ばれている。 セクション 入力ファイルやパイプのクローズを参照. このプログラムで一つ欠けているのは、本当のマクロプロセッサではできるような ネストした`@include'文を処理できないという点である。
command | getline
コマンドの出力をパイプを通じて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'のある行で指定されるコマンドに二 つ以上の同一のコマンドが指定されたときに、一回毎にそのコマンドを実行する様 にさせるために呼び出される。 セクション 入力ファイルやパイプのクローズを参照. 入力として次のデータを与える。
foo
bar
baz
@execute who
bletch
プログラムの実行結果はこうなるだろう
foo
bar
baz
hack     ttyv0   Jul 13 14:22
hack     ttyp0   Jul 13 14:23     (gnu:0)
hack     ttyp1   Jul 13 14:23     (gnu:0)
hack     ttyp2   Jul 13 14:23     (gnu:0)
hack     ttyp3   Jul 13 14:23     (gnu:0)
bletch
このプログラムはwhoコマンドを実行し、結果を出力する。(もしこのプロ グラムがやっていることを自分でやろうとしたら、その結果は異なったものとなる だろう。誰がシステムにログインしているかで変わるからだ) この記述のgetlineでは、レコードをフィールドに分割し、NFをセット して$0を再計算する。このとき、NRFNRは変更しない。
command | getline var
commandの出力をパイプを通じてgetlineに渡し、さらに変数var に格納する。例えば次に挙げるプログラムでは date ユーティリティを使用 して日付と時刻を読み込み、それをcurrent_timeという変数に格納し、さら に表示している。
awk 'BEGIN {
     "date" | getline current_time
     close("date")
     print "Report printed on " current_time
}'
この使い方のgetlineでは、どの組み込み変数も変更されず、読み込んだ レコードのフィールド分割も行われない。

入力ファイルやパイプのクローズ

もし同じファイル名や同じシェルコマンドが、awkプログラムの実行中に二度 以上getlineと一緒に使われた場合、ファイルのオープンやコマンドの実行は 最初の一回だけ行われる。つまり最初にファイルやコマンドからレコードを読み込む ときである。次にgetlineを使って読むときは同一のファイル、コマンドから 別のレコードを読み込む。

このことによって、同じファイルを再び最初から読み始めたいときや、シェルコマン ドを再実行(コマンドの出力からもっと読みだしたいとき)したいようなときには特 別な手順を踏まねばならない。つまり、次の例のようにclose関数を使わなけ ればならない。

close(filename)

あるいは

close(command)

filenamecommandといった引数は式であってもよい。その値はファ イルをオープンするときやコマンドを起動するときの文字列と等しくなければならな い。例えば、パイプを次のようにしてオープンしたならば、

"sort -r names" | getline foo

次のようにしてクローズせねばならない。

close("sort -r names")

この関数が一度呼ばれると、次にgetlineでファイルまたはコマンドから入力 しようとしたときに、ファイルの再オープンや、コマンドの再実行が行われる。

closeはクローズに成功すると0を返す。失敗した場合には0以外の 値が返るだろう。失敗したときにはまた、変数ERRNOにエラーが 起きたことを示す文字列がセットされる。


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