awkによるデータベースの作成 杉本 武(九州工業大学) 0. はじめに 言語・文学研究においては、様々なデータを扱う必要がある。その場合、市販 のデータベース・ソフトを利用するのが一般的であろう。ただ、こういったデー タベース・ソフトは、手軽である反面、難点もある。 一般的に、市販のデータベース・ソフトは、事務処理における使用が中心にな っていると言える。例えば、顧客台帳のようなものであれば、それぞれのレコー ド中のフィールドの長さは、大体定まっている。住所のフィールドを考えてみる と、レコードによってこのフィールドの長さが大幅に変わるということはない。 また、そのデータの内容も、通常の文字と数字に限られ、引用符やカンマがデー タに含まれることはほとんどない。ところが、言語・文学研究において扱われる データは、それぞれのフィールドの長さはレコード毎に様々であろう。例えば、 文献目録を考えた場合、書名は長いものもあれば、短いものもある。また、その データの中に引用符やカンマが含まれることもしばしばある。 さて、市販のデータベース・ソフトは、固定長データ形式になっているものが 多い。これを、例えば文献目録に利用した場合、書名のフィールドは、かなり長 い書名にも対応できるようにしておかなければならない(そうしないとフィール ドに入り切らない書名が出てしまう)。しかし、余裕を持ってフィールド長を設 計すると、それだけ無駄も多くなる。大きなデータを扱う場合、この無駄は無視 できない。また、レコード全体の長さにも、市販のデータベース・ソフトでは (ひと頃よりはよくなったとは言え)ある程度の制限がある(もともと事務処理 用であるため、それほど長いレコードを想定していない)。この制限は、例えば、 ある文献のテキスト自体をデータベース化する場合、障害になることがある。 一方、市販のデータベース・ソフトの中には、固定長データ形式のものに比べ ると数は少ないが、可変長データ形式のものもある。これを利用すれば、先のよ うな無駄は生じないが、また別の問題が生じる。可変長データ形式のものの場合、 フィールドの区切りを何らかの文字で示さなければならない(固定長データ形式 のものであれば、フィールド長がわかっていればいいので、必要ない)。このフ ィールドの区切りにしばしば引用符とカンマが使用される。このような場合、デ ータそのものの中に引用符やカンマを入れることができなくなったりする。 市販のデータベース・ソフトを言語・文学研究に利用するには、このような問 題があるのである。そこで、このような問題を解消できる awk を紹介したい。 1. awk とは awk とは、Alfred V. Aho、Peter J. Weinberger、Brian W. Kernighan によ って開発されたプログラミング言語である(「簡易言語」と紹介している UNIX 関連書籍もある)。"awk" という名称は、3人の開発者の頭文字からとられてい る。言語の詳細については、開発者達自らによる解説書(文献 [1])に詳しい。 ここでは、awk がどのような言語であるのか概観しておきたい。 awk は、元来、UNIX 上で開発された、データの検索・加工を行う汎用ツール である。同様な機能を持つ UNIX 上のツールとして、grep や sed があるが、 awk はこれらと異なる点を多く持つ。まず、grep は、単にデータを検索するだ けで、それを加工することはできない。それに対して、sed は、データの検索お よび加工を行うことができるが、フィールドの概念がないので、データベースを 作成するには不向きである。また、sed の場合、処理を記述した「スクリプト」 の可読性が低いので、複雑な処理を行うには、ある程度の習熟が必要になる。 これらに対して、awk は、データの検索と加工の両方が可能で、しかも、近代 的な制御構造を有しているので、プログラムのコーディングも楽である。さらに、 データベースの作成に必要なフィールドの処理自体が言語に組み込まれているの で、容易にフィールドから成るデータを扱うことができる。さらに、awk が grep や sed と異なる点は、数値も扱うことができる点である。後述するような 数値関数を用いることで、数値計算を行うこともできる。 このように、awk は、情報検索、データ変換、データの有効性のチェック、 (表)集計、レポート出力などの、様々なデータ処理に用いることができるプロ グラミング言語なのである。 この awk は、現在では、GNU プロジェクトによって開発された gawk が MS-DOS にも移植され(かつ 2バイト日本語文字も扱えるようになっている)、 パーソナルコンピュータ上でも利用できるようになっている。ここでは、gawk を MS-DOS に移植し日本語化した jgawk(serow 氏による)を使用している(文 献[2]も参照。日経 MIX, ASCII-NET PCS などで入手可能)。 awk のプログラムは、パターンとアクションの対(どちらか一方でもよい)か ら成り立っている。 パターン {アクション} パターン {アクション} ..... awk は、基本的に、ファイルから一度に 1行(1レコード)のデータを読み込み、 そのデータがパターンに合致する場合、対となっているアクションを実行する。 パターンには、grep、sed と同様な正規表現が使用できる。アクションは、C 言 語に似た言語で記述される。例えば、次のような制御構造、演算子を有する。 if 〜 (else), while 〜, do 〜 while, for 〜, break, continue, next, exit ~, +, -, *, /, %, ^, =, +=, -=, *=, /=, %=, ^=, ++, --, ?:, &&, ||, <, <=, ==, !=, >=, >, ! さらに、文字列操作、数値計算、入出力のための豊富な組み込み関数を持つ。 gsub(), index(), length(), match(), split(), sprintf(), sub(), substr() atan2(), cos(), exp(), int(), log(), rand(), sin(), sqrt(), srand() getline, print, printf(), close(), system() GNU 版 awk で拡張された、文字列操作用組み込み関数として次のようなものが ある。 tolower(), toupper() また、jgawk で拡張された、日本語文字列操作用組み込み関数として次のような ものがある。 jindex(), jlength(), jsubstr() 文字列操作関数の一部にも正規表現が使用できる。また、新たに関数を定義する こともできる。これらを用いることで、パターンと一致したデータに対して様々 な文字列操作や数値計算を行うことができる。つまり、データを検索し、それに 対して様々な処理を加えることができるわけである。 awk の最も重要な特徴は、複数のフィールドから成り立ったデータを容易に扱 えるという点である(かつ、フィールド区切り文字として、任意の文字列(正規 表現)を使用することができる)。データのフィールドへの分割は、プログラマ が管理する必要はない。awk は、ファイルから 1レコード読み込んだあと、フィ ールド区切り文字の指定に従い、自動的にフィールドに分割する。以後は、それ ぞれのフィールドに自由にアクセスすることができる($1, $2, $3, ... という 変数によって)。そして、レコードあるいはフィールドに対して、プログラムに よって様々な処理を加え、出力することができる。 2. awk によるプログラミング 次に、実例として、雑誌文献のデータ検索の 1例を考えてみよう。例えば、次 のような形式のデータファイル foo.dat があるとする('→' でタブ、'↓' で 改行を示す)。 A>type foo.dat ○○一郎→「××××××」→『△△』→40→1990↓ △△二郎→「○○○」→『×××××』→15→1985↓ ××三郎→「△△△△△△△」→『○○○』→3→1989↓ ・ ・ これは、フィールドの区切りにタブを用い、第1フィールドを著者名、第2フィー ルドを論文名、第3フィールドを雑誌名、第4フィールドを号、第5フィールドを 発行年としたものである。このデータファイルを使って、著者名で検索し、論文 名等を表示するには、次のようなプログラム foo.awk を書けばよい。 A>type foo.awk BEGIN { FS = "\t"; # … (1) } $1 ~ name { # … (2) printf( "著者名: %s\n", $1 ); printf( "論文名: %s\n", $2 ); printf( "雑誌名: %s %s号, %s年\n", $3, $4, $5 ); } "BEGIN", "$1 ~ name" の部分が先のパターン、"{}" で囲まれた部分がアクショ ンにあたる。(1) は、フィールド区切り (Field Separator) をタブにするとい う指示である。(2) は、第1フィールド ($1) と変数 name(後述)がマッチした 場合、"{}" 中の文を実行する(実際には、各フィールドのデータを出力する) という指示である。この (2) の部分は、データファイルからデータが 1行読み 込まれる毎にパターンが調べられ、マッチした場合にアクションが実行される。 これで、著者名「△△二郎」で検索するには、MS-DOS のコマンドラインで以 下のように awk を起動すればよい(実行結果とともに示す)。 A>jgawk -f foo.awk -v 'name=△△二郎' foo.dat 著者名: △△二郎 論文名: 「○○○」 雑誌名: 『×××××』15号, 1985年 "-v 'name=△△二郎'" というのは、先の変数 name に検索する著者名を代入す るものである。ここで、正規表現を使用することができる。これで、第1フィー ルドが「△△二郎」にマッチする場合にデータが表示される。 ここに挙げたプログラムは比較的単純なもので、制御構造や文字列操作関数を 一切使用していないが、最低限のデータの検索・表示が行える。さらに、制御構 造や文字列操作関数を使用することで、様々なデータ処理が可能になる。 3. awk かデータベース・ソフトか さて、awk によって通常のデータ処理は可能であるが、それでは、データベー スを構築することはできるのであろうか。まず、データベースにとって最低限な 操作は、特定データの検索・表示であろう。これは、パターンで検索したい項目 を指定し、アクションでデータの出力方法を記述すればよい。 この他に(リレーショナル・)データベースに必要不可欠な三つの操作がある。 制約(restriction) : テーブルから指定フィールドを取り出し、新しいテ ーブルを作成する。 射影(projection) : テーブルから条件に合致するレコードのみを取り出 し、新しいテーブルを作成する。 結合(join) : 特定フィールドの値の一致によって複数のテーブル のレコードを結び付け、新しいテーブルを作成する。 「制約」は、特定フィールドのみを出力するようにアクションを記述すればよい。 「射影」は、パターンによって条件を指定し出力すればよい。最後に、「結合」 であるが、これは、awk 自体ではできない。しかし、これも、awk と join(*1) を組み合わせることによって可能である。 この他、レコードのソートも awk 自体ではできないが(そのようなプログラ ムを書くことによって、できないことはないが、速度の面など現実的ではない)、 sort(*2) などと組み合わせることによって可能である。さらに、テーブルの 「併合(merge)」も、cat(*3), copy などでデータファイルを連結しソートし た後、uniq(*4) を利用することによって可能である。 このように、awk とその他の一般に流布しているツール類(*5)とを組み合わせ ることによって(リーレーショナル・)データベースに必要なほとんどの操作が 実現できるのである。 しかし、データベースを作成するのは、数多く市販されているデータベース・ ソフトによってもできる(と言うより、データベース・ソフトを利用するのが普 通である)。それでは、このようなデータベース・ソフトではなく awk を使用 することによって、どのような利点があるのであろうか。awk を使用する利点と しては、次のようなものがある。 (1) 取り扱われるデータファイルは、純粋なテキストファイルであるので、 エディタ、ワープロによって自由に読み書きすることができる。 (2) 言語自体が機種に依存せず、MS-DOS マシンであれば、どの機種でも利 用できる(また、UNIX 上のものを用いれば、UNIX マシンでの利用も可 能である)。 (3) データ構造が柔軟である(フィールド区切り文字、レコード区切り文 字をユーザーが自由に設定することができる。通常のテキストには決し て現れない制御コードを使用することもできる)。 (4) 市販のデータベース・ソフトに比べ、実行ファイルのサイズが比較的 小さいので、子プロセス機能のあるエディタ、ワープロであれば、その 中から呼び出すことができる。また、マクロ機能のあるエディタの場合、 マクロによって awk を呼び出し、結果をエディタ画面に取り込むといっ たことも可能である。 (5) 処理結果は標準出力に出力されるので、他のツールと組み合わせるこ とによって、様々な処理を加えることが可能である。 (6) 可変長データなので、データサイズが小さくなる。フィールド長が柔 軟である。 (7) 正規表現による柔軟な検索が行える。 これと共に、欠点も存在する。 (1) プログラムを組まなくてはならない(対話的処理ができない)。 (2) 処理に時間がかかる(インデックス・テーブルによる高速検索ができ ない)。 (3) 他のツールと組み合わせなければできない操作がある。 これらの欠点の中には、やり方によっては回避することができるものもある。 例えば、(1)に関して言えば、出力形式を問わなければ、次のようにコマンドラ インで指定するだけで、検索・表示を行うことができる。 A>jgawk -FS\t '$1山田/{printf("%s %s\n", $1, $2)}' foo.dat (foo.dat に「著者名」($1)、「書名」($2)、...の順でフィールドが あり、フィールドがタブで区切られているとして、著者名に「山田」を 含むレコードを検索し、著者名と書名を表示する場合) このように、凝ったことをする必要がないのであれば、多くの関数を覚えたり、 プログラムファイルを作ったりすることをしなくても、awk を使用できる。 また、(2)に関しては、ハードディスクや RAM ディスクを用いたり、高速な grep と組み合わせたりすることによって、ある程度改善できる。 (3)は、考えようによっては、利点ともなり得る。市販のデータベース・ソフ トでは、ソートの機能は、普通その中に組み込まれた形になっている。このため、 手軽にソートの機能を利用できるわけであるが、もしも、その組み込みのソート が処理速度などの点から気に入らなかったとしよう。組み込まれてしまっている 以上、他のソートと入れ換えるということはできない。これが、awk の場合、単 に自分の気に入ったソート・ツールと組み合わせればよいだけである。他のツー ルと組み合わせなければならないということは、逆に言えば、自分の気に入った ツールと自由に組み合わせることができるということでもあるのである。 4. データベース作成の実際 2. で紹介したプログラムは、簡単なプログラム例ということで、非常に単純 なことしかできない。もう少し複雑な検索・表示をするプログラム例として、国 語学会・国立国語研究所編著『フロッピー版・日本語研究文献目録(雑誌編)』 (秀英出版)を検索・表示するプログラム jbunken.awk を添付する。 これは、適当なオプションを指定することで、著者名(/a オプション)、タ イトル(/t オプション)、分類(/c オプション)による検索を行うことができ る(これらを複数指定することで、AND 検索も可)。また、5種類の出力形式を 選択することができる。例えば、コマンドラインから次のように起動することで、 データファイル bunpou.dat から、著者名に「杉本」が含まれ、タイトルに「助 詞」が含まれるデータを検索し、詳しい形式で表示する。 A>jgawk -f jbunken.awk /a杉本 /t助詞 /fa bunpou.dat 詳しくは、プログラムのコメントを参照されたい。 5. その他の利用法 1.でも述べたように、awk は、データベースの作成以外のこともできる。日常 的なプログラミングには、BASIC や C を無理に使わなくても、awk で十分な場 合が多い。例えば、文献[1]には、awk で作成した KWIC プログラムが掲載され ている。また、初めてプログラミング言語を習うといった場合でも、BASIC や C を習うよりは、awk を習った方がよいという考えもできる。awk であれば、言語 仕様が簡潔であるので、習うのもたやすいであろう。 もちろん、awk だけで全てのことができるわけではない。しかし、awk だけで はできないことであっても、他のツールを組み合せればよいのである。例えば、 awk だけでは、現在の日付や時刻を得ることはできない。しかし、これも、日付 や時刻を表示する別のツール(MS-DOS の date, time コマンドでは駄目だが) を利用すれば、awk のプログラムの中からそれを呼び出し、その出力を awk の 変数に代入するという芸当が可能である。例えば、仮に現在の時刻を表示する gettime というツールがあるとすると、次のようにすれば、時刻を変数 now に 代入することができる。 "gettime" | getline now; また、awk はプロトタイピングにも利用できる。まず awk でコーディングし、 仕様が固まった段階で、それをプロトタイプとして C に書き換えるといった使 い方もできる。入出力や正規表現の解釈など、面倒な処理は awk に任せられる ので、比較的短時間でプロトタイプを作成することができる。さらに、awk は、 C に似た言語であるので、書き換えも楽である。awk では処理速度がもの足りな いというような場合には、この方法も勧められる。 6. おわりに awk は、プログラムを書かなければならないなど、データベースを手軽に作成 し利用したいという人には不向きかもしれない。その反面、使い慣れると、様々 な柔軟なデータ処理を行うことができる。データを徹底的に利用したいという人 には、格好のツールとなるだろう。また、awk は、一度覚えてしまえば、データ ベースの作成にとどまらず、様々な作業に利用することのできる、多用途なツー ルでもある。 【注】 (*1) join : 特定のフィールドをキーとして、複数のデータファイルを結合す るツール。益山健氏作成のものがある。 (*2) sort : MS-DOS に付属のものもあるが、データベースに使用するには、特 定のフィールドについてソートを行えるものがよい。ASCII Software Tools の sort や、豊島正之氏作成の sortf などがある。 (*3) cat : ファイルの表示や連結を行うツール。ASCII Software Tools のも のなど、多数ある。 (*4) uniq : 重複したレコードを出力したり、除去したりするツール。ASCII Software Tools のものがある。 (*5) この他、データベースの処理に便利なツールとして、comm がある。これ は、二つのデータファイルを比較し、一方にしか存在しないレコードや共通 するレコードを出力するツールである。益山健氏作成のものと近藤泰弘氏作 成のものがある。 【文献】 [1] Alfred V. Aho, Brian W. Kernighan & Peter J. Weinberger, The AWK Programming Language, 1988, Addison-Welsey.(足立高徳訳『プログラム言 語 AWK』、トッパン) [2] 田中良知、「日本語 MS-DOS に対応した awk・jgawk の仕様」、『月刊アス キー』1990年7月号 【付録】 ----------------------------------------------------------------------------- # jbunken.awk Ver 1.3 # (『日本語研究文献目録』検索・表示プログラム) # # Coded by T. Sugimoto, 1990/6/23 # # Usage: jgawk -f jbunken.awk -- [options] data-files # Option: -a -t -c<class> -i (=ignore) # -f{a[ll]|b[rowser]|t[ext]|d[ata]|<others>} (=format) # -p<pager> -o<file> -& (=append) # # data-files は、環境変数 BIBPATH で指定されたディレクトリから探される。 # ただし、data-files にドライブ指定があるか、"\" で始まっている場合は、環境 # 変数を参照しない。環境変数 BIBPATH の指定は、"BIBPATH=A:\dat" と "BIBPATH # =A:\dat\" のどちらでもよい。 # -i オプションを指定すると、検索時に英大文字・小文字の区別をしない。-p # オプションを指定するとページャーを起動する。ページャー名はオプション文字 # 列か環境変数 PAGER で指定する。どちらでも指定されなかった場合は、デフォル # トとして more が起動される。-o オプションを指定すると、画面・ページャーへ # の出力と共に、ファイルにも出力する。ファイル名はオプション文字列で指定す # る。指定されなかった場合は、デフォルトとして jbunken.tmp に出力される。さ # らに -& オプションを指定すると、アペンドモードで出力される。 # オプション文字列に空白、タブなどが含まれる場合は、オプション全体を二重 # 引用符で囲む。オプションは、"-?", "/?" いずれの形式も使える。"/?" を使用 # した場合、オプションの直前の "--" は必要ない。 BEGIN { FS = OFS = "\|"; OR = 1; AND = 0; NOP = -1; TRUE = 1; FALSE = 0; ERROR = -1; STDERR = "/dev/stderr"; DEF_PAGER = "MORE"; DEF_FILE = "jbunken.tmp"; # 出力フォーマット NORMAL = 0; ALL = 1; BROWSER = 2; TEXT = 3; DATA = 4; # フィールド名 F_NO = 1 F_CLASS = 2 F_TITLE = 3 F_AUTHOR = 4 F_SOURCE = 5 F_YEAR = 6 if ( getopt() == ERROR ) { count = ERROR; exit; } if ( author ) { gsub( /\(/, "\(", author ); gsub( /\)/, "\)", author ); } if ( format != TEXT && format != BROWSER && format != DATA ) delimit = repeat( "-----", 79 ); } { # 著者名の検索 if ( author && !xmatch( $F_AUTHOR, author ) ) next; # タイトルの検索 if ( title && !xmatch( $F_TITLE, title ) ) next; # 分類の検索 if ( class && !xmatch( $F_CLASS, class, "‐" ) ) next; #出力 if ( format == TEXT ) txt_format(); else if ( format == BROWSER ) browser(); else if ( format == DATA ) pprint( $0 ); else tab_format(); count++; } END { if ( count == ERROR ) { usage(); exit( 255 ); } if ( count ) { if ( pager ) close( pager ); if ( file ) close( file ); printf( "\n%4d 件見つかりました.\n", count ) >STDERR; } else printf( "見つかりませんでした.\n" ) >STDERR; exit( !count ); } function xmatch( string, xrexp, sep, rexp, logic_op, index_or, index_and, found ) { logic_op = OR; if ( xrexp ~ /^\^/ ) { bgn_flg = TRUE; xrexp = substr( xrexp, 2 ); } else bgn_flg = FALSE; while ( xrexp ) { if ( index_or = match( xrexp, /[^\\]\$/ ) ) index_or += ( RLENGTH - 1 ); if ( index_and = match( xrexp, /[^\\]&/ ) ) index_and += ( RLENGTH - 1 ); if ( index_or == FALSE && index_and == FALSE ) { rexp = xrexp; xrexp = ""; logical_op = NOP; } else if ( index_or == FALSE \ || ( index_and != FALSE && index_and < index_or ) ) { rexp = substr( xrexp, 1, index_and - 1 ); xrexp = substr( xrexp, index_and + 1 ); logic_op = AND; } else { rexp = substr( xrexp, 1, index_or - 1 ); xrexp = substr( xrexp, index_or + 1 ); logic_op = OR; } if ( bgn_flg ) match( string, "^" rexp ) || ( sep && match( string, sep rexp ) ); else match( string, rexp ); found = RSTART; if ( logic_op == OR && found != FALSE ) { if ( ( index_and = index( xrexp, "&" ) ) != FALSE ) xrexp = substr( xrexp, index_and + 1 ); else break; } else if ( logic_op == AND && found == FALSE ) break; } return( found ); } function tab_format() { if ( count == 0 && !append ) pprint( delimit ); pprint( $F_AUTHOR ); pprint( " " substr( $F_TITLE, 1, 76 ) ); if ( length( $F_TITLE ) > 76 ) pprint( " " substr( $F_TITLE, 77 ) ); pprint( " " $F_SOURCE " " $F_YEAR ); pprint( " 【分類: " $F_CLASS "】" ); pprint( delimit ); } function txt_format() { pprint( $F_AUTHOR "、" $F_TITLE "、" $F_SOURCE "、" $F_YEAR ); } function browser() { pprint( substr( sprintf( "%-22s %s", $F_AUTHOR, $F_TITLE ), 1, 78 ) ); } function getopt( opt_cnt, bibpath, ignore ) { ignore = IGNORECASE; IGNORECASE = TRUE; bibpath = getenv( "BIBPATH" ); if ( bibpath && bibpath !~ /[\\:]$/ ) bibpath = bibpath "\\"; for ( i = 1 ; i < ARGC ; i++ ) { # -a オプション if ( ARGV[i] ~ /^[-/]a/ ) { author = substr( ARGV[i], 3 ); ARGV[i] = ""; opt_cnt++; } # -t オプション else if ( ARGV[i] ~ /^[-/]t/ ) { title = substr( ARGV[i], 3 ); ARGV[i] = ""; opt_cnt++; } # -c オプション else if ( ARGV[i] ~ /^[-/]c/ ) { class = substr( ARGV[i], 3 ); ARGV[i] = ""; opt_cnt++; } # -i オプション else if ( ARGV[i] ~ /^[-/]i$/ ) { ignore = TRUE; ARGV[i] = ""; opt_cnt++; } # -f オプション else if ( ARGV[i] ~ /^[-/]f/ ) { format = tolower( substr( ARGV[i], 3 ) ); if ( format ~ /^b/ ) format = BROWSER; else if ( format ~ /^t/ ) format = TEXT; else if ( format ~ /^d/ ) format = DATA else format = NORMAL ARGV[i] = ""; opt_cnt++; } # -p オプション else if ( ARGV[i] ~ /^[-/]p/ ) { if ( ( pager = substr( ARGV[i], 3 ) ) == "" ) if ( ( pager = getenv( "PAGER" ) ) == "" ) pager = DEF_PAGER ARGV[i] = ""; opt_cnt++; } # -o オプション else if ( ARGV[i] ~ /^[-/]o/ ) { if ( (file = substr( ARGV[i], 3 ) ) == "" ) file = DEF_FILE; ARGV[i] = ""; opt_cnt++; } # -& オプション else if ( ARGV[i] ~ /^[-/]&$/ ) { append = TRUE; ARGV[i] = ""; opt_cnt++; } # オプションが不正 else if ( ARGV[i] ~ /^[-/]./ ) { ARGV[i] = ""; opt_cnt = ERROR; break; } # ファイル名 else if ( ARGV[i] !~ /^([a-z]:|\\|[a-z_][a-z0-9_]*=)/ ) ARGV[i] = bibpath ARGV[i]; } IGNORECASE = ignore; return( opt_cnt ); } function getenv( name ) { if ( name in ENVIRON ) return( ENVIRON[name] ); else return( "" ); } function repeat( str, n, dest, res, len ) { if ( ( len = length( str ) ) > 1 ) { res = substr( str, 1, n % len ); n = int( n / len ); } dest = ""; while ( n-- ) dest = dest str; return( dest res ); } function pprint( str ) { if ( pager ) print str | pager; else print str; if ( file && append ) print str >>file; else if ( file ) print str >file; return( str ); } function usage() { print "Option: -a<author> -t<title> -c<class> -i (=ignore)" >STDERR; print " -f{b[rowser]|t[ext]|d[ata]|<others>} (=format)" >STDERR; print " -p<pager> -o<file> -& (=append)" >STDERR; } ------------------------------------------------------------------------ (『情報処理語学文学研究会会報』7、情報処理語学文学研究会、1990年)