典型的なawk
プログラムでは、すべての入力は標準入力(デフォルトではキー
ボードだが、多くの場合は他のコマンドからのパイプだろう)か、コマンドライン
で指定されたファイルから行われるだろう。もし、入力ファイルを特定したいので
あれば awk
が順に従って読み込みを行っているので、次のファイルに行く前
にすべてのデータを読み込んでしまう現在処理しているファイルの名前は組み込み
変数のFILENAME
に格納されている(セクション 組込み変数を参照)。
入力はレコードと呼ばれるユニットを単位として読み込まれ、一回にルールで処理 されるものもレコードである。デフォルトでは、レコードは一つの行である。読み 込まれたレコードは自動的にフィールド毎に分割され、ルールがそれぞれの(レコー ドの)ある部分に対して処理しやすいようにする。
まれなケースで、getline
コマンドを使って複数のファイルから入力を行う
ことを(プログラムに)明記する必要があるかもしれない。
(セクション getline
を使った入力を参照).
awk
言語はその入力を、レコードとフィールドに分割する。
レコードはレコードセパレータと呼ばれるキャラクタで分割される。
デフォルトではレコードセパレータは改行コードであり、レコードを一行の
テキストと定義している。
時として、あなたはレコードの区切りとして違ったキャラクタを使いたくなるだろ
う、そういった場合には組み込み変数RS
を変更すればよい。 RS
の値は
レコードの区切りを示す文字列であり、デフォルトでは"\n"
、つまり改行が
使われている。それは、一行をデフォルトのレコードとして扱うためである。
RS
にはその値として文字列を設定できるが、その文字列の先頭にあるキャラク
タだけがレコードセパレータとして使用され、その他のキャラクタは無視される。
他の組み込み変数が設定された値をすべて使うのに対して、 RS
だけは例外
である。
RS
の値は、awk
プログラムの中で代入演算子`='を使って変更
することができる。(セクション 代入式を参照)
新しいレコードセパレータのキャラクタは、文字列定数にするために二重引用符で
くくる必要がある。正しいタイミングでこれを行うには、プログラムの実行開始時
の、入力を処理しはじめる以前。つまり一番最初のレコードを読み込むためにセパ
レータを使う以前でなければならない。これを行うために、特殊なBEGIN
と
いうパターンを使う (セクション スペシャルパターンBEGIN
とEND
を参照)。例えば、
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'を出力す
るとその結果には新しいフィールドが含まれている。
この再計算の効果と再計算を行う機能についての説明はまだされていない。フィー
ルドの区切りを指定する出力フィールド指定子OFS
や NF
(フィール
ドの数セクション フィールドの検査を参照)についての説明もまだである。例
えば、NF
の値は新たに作り出されたフィールドのフィールド番号の最大のも
のがセットされる。
けれども、範囲外のフィールドに対する参照は$0
と NF
のどちらも
変更しない。その様な参照は空文字列を作り出す。たとえば、
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
スペシャルパターンを使う
(セクション スペシャルパターンBEGIN
とEND
を参照).
例えば、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
(このセクションでは高等な、実験的な機能について述べている。もし、あなたが
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
の主な入力ストリーム、つまり標準入力(多
くの場合は端末)かコマンドラインで指定されたファイルとしてきた。 awk
はgetline
と呼ばれる特殊な組み込み関数を持っている。この関数は明確な
制御の元に入力を行うのに使われる。
このコマンドは非常に複雑であり、初心者はあまり使うべきではない。この章が入
力に関してのものであるのでここで記述している。以下に挙げる例はまだ説明され
ていないものが含まれている getline
コマンドの説明である。従って、この
マニュアルの残りの部分を読んでawk
がどのように動作するかの知識を十分
得た後に、ここに戻ってgetline
コマンドを学ぶのが良いだろう。
getline
はレコードが見付かれば1を返し、ファイルの終端に達したときには
0を返す。もし、レコードの読み込みでなんらかのエラーが発生したり、
ファイルがオープンできなかったりした場合にはgetline
は-1を
返す。このときgawk
はERRNO
という変数にエラーが発生したことを
表わす文字列をセットする。
次の例では、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'を他の文に置き換えることによって、
コメントアウトされた入力に対してある正規表現にマッチするかどうかを探
すなどして、より複雑な処理を行うことができる (このプログラムには隠された問題点がある。わかる
かな?)。
こういった使い方のgetline
はNF
(フィールドの数、
セクション フィールドの検査を参照)、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 }'通常の入力ストリームを使わないので、
NR
とFNR
の値は変更されない。
しかし、読み込まれたレコードは通常のルールに従ってフィールドに分割され、
$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
を再計算する。このとき、NR
とFNR
は変更しない。
command | getline var
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)
filename や commandといった引数は式であってもよい。その値はファ イルをオープンするときやコマンドを起動するときの文字列と等しくなければならな い。例えば、パイプを次のようにしてオープンしたならば、
"sort -r names" | getline foo
次のようにしてクローズせねばならない。
close("sort -r names")
この関数が一度呼ばれると、次にgetline
でファイルまたはコマンドから入力
しようとしたときに、ファイルの再オープンや、コマンドの再実行が行われる。
close
はクローズに成功すると0を返す。失敗した場合には0以外の
値が返るだろう。失敗したときにはまた、変数ERRNO
にエラーが
起きたことを示す文字列がセットされる。