もっともよく行われるアクションの一つは、入力の一部もしくは全部を出力するこ
とである。単純な出力のためにprint
文を使い、整形して出力するために
printf
文を使う。この両者はこの章で説明する。
print
文
print
文は出力を単純に、標準的な書式で行う。カンマで区切られたリストに
なっているデータを文字列とするか数値として出力するかを特定できるだけである。
出力は一つのスペースで区切られ、最後に改行が加えられる。例を挙げよう。
print item1, item2, ...
アイテムのリストは、全体を括弧でくくる事もできる。この括弧は、アイテムのいず
れかが関係演算子を使った式である場合には使う必要がある。そうしないと、リダ
イレクションと混同してしまう可能性があるだろう
(セクション print
やprintf
の出力のリダイレクトを参照).
関係演算子には `==', `!=', `<', `>', `>=', `<=',
`~' そして `!~' がある。
(セクション 比較式を参照).
プリントの対象となるアイテムは文字列定数や数値定数、カレントレコードのフィー
ルド($1
のような)、変数、式である。 print
文は出力する値を完全
に計算する。二つの例外として、どのように出力するか、つまりどの位カラムを出力
するか、指数表示を使うか使わないか等を指定することはできない
(セクション 出力セパレータを参照, と
セクション print
を使った数値出力の制御を参照.)
そういったときにはprintf
文を使う必要がある
(セクション printf
文を使った Fancier Printingを参照)。
単に`print' とだけ書かれて何もアイテムのない`print' 文は `print $0'と同じ動作、つまりカレントレコード全体を出力する。空行を出力 するには、`print ""'として、出力するコードにnullまたは空文字列を指定す る。
固定したテキストを出力するために、"Hello there"
の様な文字列定数
を使う。ここで二重引用符を忘れた場合、そのテキストは式として扱われて、おそ
らくはエラーとなるだろう。また、二つのアイテムの間にはスペースが出力される
ことを覚えておいて欲しい。
ほとんどの場合、各々のprint
文は一行の出力を作り出す。しかし、一行に
限らない。もし、アイテムの値が改行を含む文字列であった場合、改行は残りの文字
列とともに出力される。このやり方で、一つのprint
で多くの行を出力するこ
とができる。
print
文の例次に挙げるのは改行が埋め込まれた文字列の出力の例である。
awk 'BEGIN { print "line one\nline two\nline three" }'
出力はこうなる。
line one line two line three
次の例はカレントレコードの最初の二つのフィールドを空白で区切って出力する。
awk '{ print $1, $2 }' inventory-shipped
出力はこうなるだろう。
Jan 13 Feb 15 Mar 15 ...
print
文を使うときにありがちな間違いは、二つのアイテムの間に置かれるカ
ンマを忘れてしまうというものである。それはスペースなしに、一つにまとまって出力
される結果となるだろう。なぜなら、二つの並んだ文字列というのはawk
で
は連接を行う式を意味するからだ。カンマがない例として、
awk '{ print $1 $2 }' inventory-shipped
出力はこうなる。
Jan13 Feb15 Mar15 ...
もう一つの例は、 `inventory-shipped'にあまり馴染みのない人に対してもわ
かりやすい出力をする、というものである。最初のヘッダ行で何を出力しているか
を明確にしようとして、月ごとの量($1
)と、緑のクレーの積込量
($2
)のテーブルにヘッダを付け加えている。これをBEGIN
パターン
(セクション スペシャルパターンBEGIN
とEND
を参照)
を使って出力の最初に一回だけ出力させている。
awk 'BEGIN { print "Month Crates" print "----- ------" } { print $1, $2 }' inventory-shipped
どう出力されるか予想できるだろうか? このプログラムの出力はこうなる。
Month Crates ----- ------ Jan 13 Feb 15 Mar 15 ...
ヘッダとデータのテーブルがきちんと揃っていない! しかし、二つのフィールドの 間に固定長のスペースを出力するようにできる。
awk 'BEGIN { print "Month Crates" print "----- ------" } { print $1, " ", $2 }' inventory-shipped
このカラムの揃え方では、多くのカラムがあった場合に揃えるのが少々面倒になることが
想像できるだろう。二、三個のカラムのためなら単純にスペースを数えられるだろう
が、これがもっと多かったりすると数え間違えやすくなる。それが
printf
文が作られた理由である。
(セクション printf
文を使った Fancier Printingを参照)
その機能の一つにデータのカラムをそろえることがある。
先に述べたように、print
文はカンマで区切られたアイテムのリストから構成
される。出力では、アイテムは普通に一つのスペースで区切られる。しかし、スペー
スでなければならないというわけではなく、単に一つのスペースがデフォルトである、
ということである。組み込み変数のOFS
に設定することによって、 出力
フィールドセパレータを任意の文字列にすることができる。この組み込み変数の初
期値は" "
という文字列、つまりただ一つの空白である。
完全なprint
プリント文の出力は、出力レコードと呼ばれる。個々の
print
文は一つの出力レコードを出力し、その後に出力レコードセパレータ
と呼ばれる文字列を出力する。組み込み変数のORS
はこの文字列を規定する。
この変数の初期値は改行キャラクタからなる"\n"
という文字列である。従っ
て、通常個々のprint
文は行を分けるのである。
組み込み変数のOFS
や ORS
に新しい値を代入することによって、出力
フィールドや出力レコードをどのように区切るかを変更することができる。このよ
うな操作は通常BEGIN
ルールの中で行われる。通常これはBEGIN
ルー
ルの中で、入力が処理される前に行われる
(セクション スペシャルパターンBEGIN
とEND
を参照)。
この動作を入力ファイル名の前に置かれたコマンドライン上での代入で行うことも
できる。
次の例は入力レコードの一番目と二番目のフィールドをセミコロンで区切り、各行 の後に空行を加えて出力する。
awk 'BEGIN { OFS = ";"; ORS = "\n\n" } { print $1, $2 }' BBS-list
ORS
に改行が含まれていなかった場合、別の手段で陽に改行を
出力しない限り、全ての出力は一つの行にまとめて行われるだろう。
print
を使った数値出力の制御
print
文を数値を出力するために使った場合、awk
は内部で数値を
(その数値を表す)文字列に変換し、その文字列を出力する。 awk
はこの変
換動作のためにsprintf
関数を使用する。現在のところ、sprintf
関数
が数値(や文字列)をどのように書式化するかを記述されている 書式指定
を受諾し、数値の書式指定は違った方法が何種類もあるということ
だけで十分である。書式の違いは
セクション printf
文を使った Fancier Printingを参照.
で詳しく説明されている。
組み込み変数OFMT
にはデフォルトの書式指定が格納されていて、出力すると
きに数値を文字列に変換する必要があるときにprint
文が sprintf
と共
に使用する。 OFMT
の値として異なった書式指定を与えることによって、
print
文がどのように数値を出力するかを変更できる。簡単な例を挙げよう。
awk 'BEGIN { OFMT = "%d" # 数値を整数として出力 print 17.23 }'
この例では`17'が出力されるだろう。
printf
文を使った Fancier Printing
print
で出力するには複雑な書式を使いたいという場合には、printf
を使うと良い。 printf
を使うことによってアイテムごとにその幅を指定し
たり、数値を出力するのにそれをどのようなスタイルで出力するのかを指定するこ
とができるようになる(使用する基数や、符号を出力するかどうか、小数点以下何
桁まで出力するか)。他の引数をどのように、どこに出力するかを制御する
書式文字列と呼ばれる文字列によって、それを行なうことが可能である。
printf
文printf format, item1, item2, ...
リスト全体を括弧でくくる事もできる。括弧はアイテムである式が関係式を使ってい
る場合には必要になる。括弧を使わないとリダイレクションと混乱するかもしれない
(セクション print
やprintf
の出力のリダイレクトを参照).
関係演算子には `==', `!=',
`<', `>', `>=', `<=', `~', `!~'
がある(セクション 比較式を参照).
printf
とprint
の違いはformatという引数にある。この引数は
文字列として与えられ、他の引数をどのように出力するかという指定を行う。この
ような引数は書式文字列と呼ばれる。
この書式文字列は ANSI Cライブラリのprintf
関数のものと同じである。大
部分の書式は(書式文字列の)文字通りに出力されるテキストである。書式指定子
はアイテム一つ毎に一つの割合で、このテキスト中に置かれている。個々の書
式指定子は、次のアイテムをどのような書式で出力するかを指定している。
printf
は自動的に改行を付け足すような動作はせず、書式文字列で指定さ
れた通りに出力を行う。だから、改行を出力したい場合には、書式文字列に改行コー
ドを含めなければならない。出力時のセパレータを指示する変数 OFS
と
ORS
は、printf
文の実行結果に対して何の影響も及ぼさない。
書式指定子は`%' で始まり、(printf文でどのように出力するかを指定する) 書 式指定文字で終わる(`%'そのものを出力したい場合には `%%'と記述する)。 書式指定文字は値をどのように出力するかを指定する。書式指定子の残りは、どの位 のフィールド幅で出力するかというような事を指定する(省略可能な) 修飾子である。
以下は書式指定文字のリストである。
printf "%4.3e", 1950小数点以下を含めて全部で四つの数字からなる `1.950e+03'を出力する。 `4.3' は 修飾子(modifiers)である(後述)。
printf
書式の修飾子書式指定には出力されるアイテムの値と、出力する大きさを制御するために 修飾子 を含めることができる。修飾子は`%'と書式指定文字の間に置かれる。 以下に使うことのできる修飾子を挙げる。
printf "%-4s", "foo"`foo 'が出力される。
printf "%4s", "foo"` foo'が出力される。 width は(出力の)最小幅であって、最大幅ではない。アイテムの値が width よりも多くのキャラクタを必要としたならば、出力はその幅となる。 したがって、
printf "%4s", "foobar"この出力は `foobar'となる。 マイナス記号を伴ったフィールド幅指定は、幅あわせのためのスペースを フィールドの左ではなく右に置く。
Cライブラリのprintf
は実行時にwidthとprecを指定すること
(例えば"%*.*s"
のように)ができる。書式文字列中でwidth や
precを記述する代わりに、引数リストでそれを渡す事ができる。例えば
w = 5 p = 3 s = "abcdefg" printf "<%*.*s>\n", w, p, s
これは次の書き方と等しい。
s = "abcdefg" printf "<%5.3s>\n", s
上のプログラムは両方とも`<**abc>'を出力する(こ こでスペースを表すのに"*" というシンボルを使って、出力に二つのス ペースがあることがはっきりわかるようにしている)。
awk
の初期のバージョンではこの機能がサポートされていなかった。次の様
に書式文字列を組み立てるのに文字列連接を使う事で、この機能をシミュレートす
る事が可能である。
w = 5 p = 3 s = "abcdefg" printf "<%" w "." p "s>\n", s
しかし、これは読み易いとは云えない。
printf
を使った例
次の例はprintf
を使ったテーブルを揃える方法である。
awk '{ printf "%-10s %s\n", $1, $2 }' BBS-list
ファイル`BBS-list' のBSSの名前($1
)を十文字の長さで左寄せをして
出力する。そしてその後ろに電話番号($2
)を出力する。この例は名前と電
話番号の二つのカラムを持つテーブルを桁揃えして出力する。
aardvark 555-5553 alpo-net 555-3412 barfly 555-7685 bites 555-1675 camelot 555-0542 core 555-2912 fooey 555-1234 foot 555-6699 macfoo 555-6480 sdace 555-3430 sabafoo 555-2127
なぜ電話番号を数値として出力するように指定していないのかわかるだろうか? ここ では数字がダッシュで区切られているので文字列として出力している。もし、これを 数値として出力しようとすると、ダッシュは減算の記号として解釈されてしまうだろ う。そうなると結果はおかしなものとなってしまうだろう。
ここで、電話番号は行の最後にあるので出力幅を指定していない。電話番号の後ろに スペースを出力する必要はないからだ。
先頭のカラムにヘッダを付け加えることによってテーブルをより見やすいものに
している。これを行う為に、BEGIN
パターン
(セクション スペシャルパターンBEGIN
とEND
を参照)
を使ってヘッダをawk
プログラムの始めで一度だけ出力している。
awk 'BEGIN { print "Name Number" print "---- ------" } { printf "%-10s %s\n", $1, $2 }' BBS-list
この例ではprint
文と printf
文を混ぜて使っているが
気がついただろうか?、同じ結果をprintf
文だけを使って得る事ができる。
awk 'BEGIN { printf "%-10s %s\n", "Name", "Number" printf "%-10s %s\n", "----", "------" } { printf "%-10s %s\n", $1, $2 }' BBS-list
ヘッダと同じ書式指定を使って各カラムを出力しているので、ヘッダと桁揃えされる。
三回同じ書式を指定するのに変数に書式を代入してその変数で指定する方法がある。 実例を挙げよう。
awk 'BEGIN { format = "%-10s %s\n" printf format, "Name", "Number" printf format, "----", "------" } { printf format, $1, $2 }' BBS-list
ためしに、前のprint
文(セクション print
文を参照)の節
で説明した`inventory-shipped'の例の、表題と表のデータを
printf
文でそろえてみてほしい。
print
やprintf
の出力のリダイレクト
これまでは出力は標準出力に対してのみ行われていた。
print
と printf
の両方とも別の場所に出力を送ることができる。
これはリダイレクトと呼ばれる。
リダイレクションはprint
文か printf
文の後に書く事ができる。
awk
におけるリダイレクションはシェルコマンドのリダイレクションとよく似
ているが、awk
ではプログラムの中にリダイレクションの指示を書くという点
は違っている。
以下に、三種類の出力リダイレクションの例を挙げる。例ではprint
文が使わ
れているが、printf
文を使っても同じように動作する。
print items > output-file
awk '{ print $2 > "phone-list" print $1 > "name-list" }' BBS-list
print items >> output-file
print items | command
awk
の式でも良い。
その式の値はコマンドを実行するための文字列に変換される。
次の例では二つのファイルが作られる。一つはソートされていないBBSの名前のリスト。
もう一つはアルファベットの降順にソートされたリストである。
awk '{ print $1 > "names.unsorted" print $1 | "sort -r > names.sorted" }' BBS-listこの例のソートされていないリストは、ソート済みリストがパイプを通って
sort
ユーティリティが出力する間に、通常のリダイレクトで書き込まれる。
次の例はリダイレクトを使って、メイリングリスト`bug-system'に従ってメイ
ルを送るものである。これはトラブルにあったときに、システムメンテナンスの為に
定期的にawk
スクリプトを実行するような場合に便利だろう。
report = "mail bug-system" print "Awk script failed:", $0 | report print "at record number", FNR, "of", FILENAME | report close(report)ここで
close
関数を呼んでいるのは、すぐにパイプを閉じて出力を送る為であ
る。 セクション 出力ファイルやパイプのクローズを参照. により詳しく記述さ
れている。この例はファイル、コマンドを与えるのに変数を使っている。つまり常に
文字列定数を使う、というわけではないということである。 awk
が毎回文字
列値を要求するので、変数を使うというのは一般にはいいアイデアである。
`>', `>>', `|' を使って出力をリダイレクトするときに、file や commandがそれまでにプログラム中で使っていなかったり出力する直前にク ローズを行っていたりすると、ファイルやパイプをオープンする為にシステムに問い 合わせを行う。
ファイルやパイプがオープンされたとき、そのファイル名やコマンドは awk
によって記憶されていて、後で同じファイルや同じコマンドに出力を行ったときには
先程の出力に追加して出力する。ファイルやパイプはawk
が実行終了するまで
オープンされている。これは大概の場合はうまくいく。
ときとして出力ファイルやパイプを早めにクローズしなければならないときがある。
この為にclose
関数を使う。例を挙げよう。
close(filename)
あるいは
close(command)
引数のfilename や commandは、その値がファイルやパイプをオープン する為に使える文字列であるならば式の形をとっていてもよい。例えば、次のように パイプをオープンしたならば
print $1 | "sort -r > names.sorted"
次のようにクローズを行わなければならない。
close("sort -r > names.sorted")
出力ファイルをクローズする必要がある理由にはいくつかある。
awk
プログラム中で、書き込みを行ったファイルを後で読み返す為。最
後の出力が終わったときにファイルをクローズした後で、 getline
を使って
そのファイルから入力を始める事ができる
(セクション getline
を使った入力を参照)。
awk
プログラム中で、連続的に複数のファイルに書き込みを行う為。も
しファイルのクローズを行わないでいると、一つのプロセスでオープンできるシステ
ムの限界数を越えてしまうかもしれない。それを避ける為に書き込みが終了したとき
にそれをクローズする。
mail
プログラムに出力を送る
と、そのメッセージはパイプがクローズされるまで実際には送られない。
mail
プログラムに送ることを考えてみよう。クロー
ズしないでこのパイプに数行の出力をリダイレクトすると、一つのメッセージに数行
があるという結果となるだろう。対照的に、各行を出力した後にパイプをクローズす
ると、それぞれの行は別々のメッセージに分割される。
close
はクローズに成功した時0を返し、失敗したときに0以外を返す。
失敗した場合には変数ERRNO
にエラーの発生を示す文字列がセットされる。
実行中のプログラムは、慣習的に三つの入出力ストリームを何もしなくても読み込み、 書き込みのために使えるようになっている。それらは標準入力, 標準出 力, 標準エラー出力として知られている。これらのストリームはデフォルト でターミナルの入出力に割り当てられている。しかし、しばしばシェルでのリダイレ クトによって(`<', `<<',`>', `>>', `>&' `|'と いったオペレータを使って)、変更される。標準エラー出力はエラーメッセージの出 力にだけ使われる。それは、標準出力、標準エラー出力の二つのストリームがあり、 別々にリダイレクトを行う事が可能であるからである。
他のawk
処理系では、次のような方法でしか awk
プログラ
ムから標準エラー出力にエラーメッセージを出力する事ができない。
print "重大なエラーが発生しました!\n" | "cat 1>&2"
これは標準エラー出力にアクセスするためにパイプを開いてawk
プロセスの出
力を渡している。これはエレガントとは言い難いし、他のプロセスを必要とする非効
率的なやり方である。多くの人はこの点にあまり気を使っていないようなawk
プログラムを書いている。こういったやり方に代えて、エラーメッセージを次のよう
にしてターミナルに送る。
NF != 4 { printf("%d行目をスキップしました: フィールド数が4つでありません\n", FNR) > "/dev/tty" }
これは多くの場合同じ結果となるだろうが、常に、ではない。標準エラー出力は通常
ターミナルであるが、リダイレクトすることができ、その場合には出力はターミナル
に対しては行われない。事実、awk
をバックグラウンドジョブで実行すると、
そのプロセスはターミナルを持っておらず、`/dev/tty'のオープンは失敗する。
gawk
は三つの標準ストリームにアクセスするために特殊なファイル名を持っ
ている。gawk
で入出力をリダイレクトするときにリダイレクト先のファイル
名が次の特殊な名前の中にあれば、gawk
は直接(定義されている)ストリー
ムを使用する。
awk
を実行しているプログラムによって(典型的なのはシェル)、オープンされていなけ
ればならない。特別な操作をしない限り、ディスクリプタの0,1,2だけが使用可能で
ある。
`/dev/stdin', `/dev/stdout', `/dev/stderr'等のファイル名は
それぞれ`/dev/fd/0',
`/dev/fd/1', `/dev/fd/2'の
別名になっている。
gawk
プログラムでエラーメッセージを出力する適当な手段は次の様に
`/dev/stderr'を利用することである。
NF != 4 { printf("line %d skipped: doesn't have 4 fields\n", FNR) > "/dev/stderr" }
gawk
はgawk
が実行されているプロセスの情報にアクセスするために特
殊なファイル名を与えられている。これらの"ファイル"はそれぞれ、ひとつの情報
のレコードを表わしている。二度以上読み込むためには最初にclose
をつかっ
てクローズしなければならない。
(セクション 入力ファイルやパイプのクローズを参照) ファイルの名前は
$1
getuid
システムコールの値。
$2
geteuid
システムコールの値
$3
getgid
システムコールの値
$4
getegid
システムコールの値
getgroups
システムコールが
返すグループIDである(マルチプルグループはすべてのシステムでサポートされて
いるわけではないだろう)。
これらの特殊ファイル名はコマンドライン上で、
awk
プログラム中でI/Oリダイレクトされるデータファイルのように
使われ、`-f'がついたソースファイルのようには使われないだろう。
これらの特殊なファイル名の認識は互換モードで動作しているときには
無効となる(セクション awk
の起動を参照).
警告: あなたの使うシステムが`/dev/fd'というディレクトリ (あるいは先に述べたような特殊ファイル)を持っていないのであれば、これらのファ イル名を
gawk
それ自身が解釈する。例えば、 `/dev/fd/4'を使ってファ イルディスクリプタの4 番に出力しようとすると、ファイルディスクリプタの4番がdup
され、その結果の新しいファイルディスクリプタに出力が行なわれる。こ のことはほとんどの場合重要なことではないが、重要なのはファイルディスクリプタ の0、1、2番に関係するファイルをクローズしないということである。もし、これら のうちどれかをクローズしてしまったならば、その結果は予測できないものとなる。