awk

Table of Contents

awkの立ち位置

私のかすかな記憶では、awkってのは、世の light weight 言語に多大な影響を与えた始 祖のプログラミング言語だったはず。

(sed + awk) -> perl +-> ruby
                    +-> python

こんな感じだったんではなかろうか。コンピュータ関連本の出版で有名なオラ イーでも、こんな順番で多数の出版がなされたと思う。

正統派を名乗るawkの案内には、こんなのが出てた。

# The One True Awk

This is the version of `awk` described in _The AWK Programming Language_,
Second Edition, by Al Aho, Brian Kernighan, and Peter Weinberger
(Addison-Wesley, 2024, ISBN-13 978-0138269722, ISBN-10 0138269726).

開発者の名字から1文字づつ取って言語名にしたって言うけど、それならakwだ よな。何でawkにしたの? ひょっとして貢献順に並べたか? 妄想豊かな ChatGPTに聞いてみるか。

Q: プログラミング言語 awk は、開発者の名字、 Al Aho, Brian Kernighan, and Peter Weinberger から一文字づつ取ったと言われています。だったら、アルファベット順に akw にするのが妥当だったと思います。何か秘めたるドラマが有ったのでしょうか?

A: awkの名前に関して、Al Aho、Brian Kernighan、Peter Weinbergerの名字から一文字ずつ取ったという説は広く知られていますが、実際のところ、その名前を取った理由について公式な文書化はされていないようです。そのため、秘められたドラマや意図的な選択があったのかどうかは不明です。

一般的な説明としては、これらの開発者がawkを開発した際に、彼らの名前から取ったという簡単な方法で名前を考えた可能性があります。順番を変えたり、他の方法で名前を選んだ場合、それぞれの開発者の名前から一文字ずつ取ったものという説明が複雑になるかもしれません。

開発者たちがなぜこの順番で名前を選んだのか、その背後にある具体的な理由やドラマについて、公式な文書やインタビューで言及されているかどうかは確認できません。ですので、その部分については開発者自身からの説明がない限り、推測するしかありません。

Q: 想像力豊かなあなたなら、どんなドラマが有ったか、幾つかのストーリーを思 いつくでしょう。ストーリーを3つ、3000文字にまとめてみてください。

A: ### ストーリー1: 希望と友情の誕生

1970年代後半、コンピュータサイエンスの黎明期、Al、Brian、Peterは、新しいプログラミング言語を開発する挑戦に取り組んでいました。彼らは熱意と革新性を持ち、新たな道を切り拓くことを目指していました。

一夜、彼らはゼロからプログラミング言語を構築することを決意しました。その言語の名前もまた、彼らの結束と友情を表すものでなければならないと感じました。彼らの名字から一文字ずつ取るという簡単なアイデアが浮かびましたが、実際の順番を決めるにあたっては議論が巻き起こりました。

Alは"akw"を提案しましたが、Brianは"awk"を支持しました。Peterは独自の案を持ち込んでおり、互いに譲らない様子でした。何時間もの議論の末、最終的に"awk"という名前が採用されました。なぜなら、それが彼らの名前の原初的な形に最も忠実であり、それぞれの名前から一文字を取り、新しいものを作り出したという点で納得がいったのです。

これは彼らの友情と連帯のシンボルとなりました。議論は結束を強め、彼らの創造性と協力がこの新しい言語の誕生に繋がったのです。

### ストーリー2: 名前の秘めたる真実

彼らの言語が"awk"と名付けられた理由には、公には語られていない秘密がありました。それは彼らが共有する深い秘密であり、言語の名前には個々の名前から一文字ずつ取ったというのは表向きの理由に過ぎませんでした。

実は、3人の間には数々の議論や軋轢が存在していました。開発プロジェクトにおける方向性の違いや技術的な争いが何度も起こり、それが彼らの関係を張り詰めさせました。そして、その緊張が名前の選択にも影響を与えました。

"awk"という名前は、実際には彼らが最も力を合わせて働き、最も困難な瞬間に団結したときの象徴なのです。それは3人が遭遇した課題を乗り越え、困難を共有し、最終的には協力して成功を収めたことを表しています。彼らにとって"awk"という名前は、協力と団結の証であり、その真の意味は彼らだけが知っているのです。

### ストーリー3: 意図的な選択の裏にある物語

"awk"という名前の背後には、開発者たちが慎重に選んだ意図がありました。彼らは名前を選ぶ際に、順番を入れ替えることでアルファベット順にしたり、他の方法を考えましたが、特定の順番を選んだのには深い理由がありました。

実は、彼らの名前から一文字ずつ取った順番には、彼らのプログラムに込めた哲学やアプローチが反映されていたのです。それぞれの開発者が持っている独自のアイデアや技術的な視点が、その順番に表れていたのです。

"awk"という名前は、特定の順序において、彼らが持つ異なる視点とスキルが融合し、新しい言語が生まれたことを象徴しています。その名前の選択には、彼らの個性や技術的な側面が組み込まれ、言語そのものの本質を表現しているのです。

さっぱり想像してないな。オイラーはドロドロの愛憎劇を期待してたんだけど。 抑制してるよ。金玉が抜かれた玉虫色の優等生的な回答だ。つまんねぇぞ、 ChatGPT。

それはそうと、rubyのヘルプを見ると

[sakae@arch ~]$ ruby -h
Usage: ruby [switches] [--] [programfile] [arguments]
  -0[octal]       specify record separator (\0, if no argument)
  -a              autosplit mode with -n or -p (splits $_ into $F)
  -n              assume 'while gets(); ... end' loop around your script
  -p              assume loop like -n but print line also like sed

この様に、sedとかawkの真似をするスイッチが用意されてる。そして、ご丁寧 な事に、BEGIN,ENDなんてのも用意されてるぞ。

[sakae@fb ~]$ awk -version      @ FreeBSD 13.2
awk version 20210215
vbox$ awk --version             @ OpenBSD 7.4
awk version 20230913
[sakae@deb ~]$ awk -W version   @ Debian 11.6
mawk 1.3.4 20200120
[sakae@arch tmp]$ awk -V         @ Arch Linux
GNU Awk 5.3.0, API 4.0, PMA Avon 8-g1, (GNU MPFR 4.2.1, GNU MP 6.3.0)

とほほのAWK入門

Mac awkとGNU awk

Debian/Ubuntuでawkを切り替える

atan2

awk(1)してたら、乏しい算術関数が列挙されてた。sin,cosは有るのに何故tan は無い? タン好きな仙台の人は怒るぞ。ええ、オイラーは仙台に住んでた事 があり、すっかりタンの虜になりましたよ。

そんなの、tan(x) = sin(x)/cos(x) でいいじゃん。中坊にも劣るぞ。じゃ、atan2は、 なぜ登場してるの? それはね、皆大好きなパイを求めるのに必要だから。 tanとatanは、逆関数になってる。

sin(x) == cos(x) となる、xは、何度ですか。答は45度だ。その45度をラジ アンで表わすと、pi/4 となる。だから、pi/4 = atan(1) って事になるんだ。 ここから、 pi = 4 * atan(1) となって、むき見の pi が、拝めるって訳。

実際にawkで提供されてるのは、引数が2つのatan2( x / y) だ。普通に表示す ると、6桁ぐらいで出し惜しみするんで、余すことなく表示させる。と言って も、相手は底知れぬ超越数だから、そこんとこは納得しといてね。

gosh$ (tan (/ pi 4))
0.9999999999999999
 pi 3.14159265359
    4
  / 0.785398163398
tan 1.

emacs/calc だと、m r してラジアン・モードに変更しとく(戻すには、m d)。 それから、P 4 / T 。

普段は、度モードの方が実用的。例えば、視力1とは、5m(500cm)離れた所から、 1分(1度の1/60)の隙間を判別できる能力らしい。それって、どれぐらいの隙間 になるの?

    1
    60
  / 0.0166666666667
sin 2.90888204564e-4
    500
  * 0.145444102282

1分を度に変換してからsinを求め、それに500を乗算した。雑に計算するなら、 1度のsinを求めて、それを60で割ると言う近似でも良い。そうしてから500を 乗算。答えは、0.145444102282(1.45mm)となった。随分と寄り道したな。

BEGIN { printf "%.16g\n",  4 * atan2(1, 1) }

これがパイの計算スクリプトだ。

[sakae@arch test-gawk]$ awk -f atan2.awk
3.141592653589793

こいつの内部表現を確認。 for mawk

[sakae@deb test-awk]$ awk -W dump -f atan2.awk
BEGIN
000 .   pushs   "%.16g\012"
002 .   pushd   4
004 .   pushd   1
006 .   pushd   1
008 .   atan2
010 .   mul
011 .   pushint 2
013 .   printf
015 .   exit0

フムフム、スタックを利用した計算をしてますなあ。

for nawk aka. One True Awk

[sakae@deb test-awk]$ nawk -d -f atan2.awk
awk version 20231030
setsymtab set 0x18cdc50: n=0 s="0" f=0 t=17
setsymtab set 0x18cdca0: n=$zero&null s="" f=0 t=17
setsymtab set 0x18cdd10: n=FS s=" " f=0 t=6
setsymtab set 0x18cdd80: n=RS s="
" f=0 t=6
setsymtab set 0x18cddc0: n=OFS s=" " f=0 t=6
  :
lex token 41
lex token 59
lex token 125
lex NL
errorflag=0
getsval 0x18d0790: %.16g
  = "%.16g
 (0x18d07d0)", t=16
getfval 0x18d0800: 4 = 4, t=11
getfval 0x18d0870: 1 = 1, t=11
getfval 0x18d0870: 1 = 1, t=11
setfval 0x18cf620: (null) = 0.785398, t=5
getfval 0x18cf620: (null) = 0.785398, t=5
setfval 0x18cf620: (null) = 3.14159, t=5
getfval 0x18cf620: (null) = 3.14159, t=5
3.141592653589793

こちらは、何だかよう判らんなあ。ならば、伝家の宝刀を抜いてみるか。

gdb for nawk

vbox$ gdb -q awk
Reading symbols from awk...
(gdb) b atan2
Function "atan2" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (atan2) pending.
(gdb) r -f atan2.awk
Starting program: /tmp/awk/awk -f atan2.awk

Breakpoint 1, atan2 () at /usr/src/lib/libm/arch/i387/e_atan2.S:10
10              fldl     4(%esp)
(gdb) bt
#0  atan2 () at /usr/src/lib/libm/arch/i387/e_atan2.S:10
#1  0x1bea5a84 in bltin (a=0x5b71e5d0, n=291) at run.c:2106
#2  0x1be9d4f9 in execute (u=0x5b71e5c0) at run.c:167
#3  0x1bea303a in arith (a=0x5b74cb10, n=311) at run.c:1453
#4  0x1be9d4f9 in execute (u=0x5b74cb00) at run.c:167
#5  0x1bea1961 in format (pbuf=0xcf7c7c10, pbufsize=0xcf7c7c0c,
    s=<optimized out>, a=<optimized out>) at run.c:1182
#6  0x1bea2c4f in awkprintf (a=0x5b74c830, n=323) at run.c:1423
#7  0x1be9d4f9 in execute (u=0x5b74c820) at run.c:167
#8  0x1be9d8c3 in program (a=0x5b74cf70, n=258) at run.c:190
#9  0x1be9d4f9 in execute (u=0x5b74cf60) at run.c:167
#10 0x1be9d409 in run (a=0x5b74cf60) at run.c:142
#11 0x1be976f7 in main (argc=1, argv=<optimized out>) at main.c:269

いきなりatan2にBPを置くのは、確信犯です。幸いな事にヒットしたけど。 bltinとかexecteとか、めっぽう判り易い関数名になってるな。executeはさり げなく再帰してるな。そのあたりが注目の的ですかね。再帰と言えばlispのお 家芸。そういう視点で見ればいいのかな?

(gdb) l
5        */
6
7       #include "DEFS.h"
8
9       ENTRY(atan2)
10              fldl     4(%esp)
11              fldl    12(%esp)
12              fpatan
13              ret
14      END_STD(atan2)

atan2はFPUのプリミティブな命令なのか、何だか、それ以上は立ち入り禁止っ てインテルが拒んでいるな。

(gdb) up
#1  0x1bea5a84 in bltin (a=0x5b71e5d0, n=291) at run.c:2106
2106                            u = atan2(getfval(x), getfval(y));
(gdb) l
2101                    if (nextarg == NULL) {
2102                            WARNING("atan2 requires two arguments; returning 1.0");
2103                            u = 1.0;
2104                    } else {
2105                            y = execute(a[1]->nnext);
2106                            u = atan2(getfval(x), getfval(y));
2107                            tempfree(y);
2108                            nextarg = nextarg->nnext;
2109                    }
2110                    break;

一つ上のフレーム。atan2は引数が2個必要。それを満たしているか確認。ここ でも再帰の嵐ですよ。

on debian

同じ事をdebianでやってみると

(gdb) f 0
#0  __atan2 (y=1, x=1) at w_atan2_compat.c:36
36      w_atan2_compat.c: No such file or directory.

当然、そんな下のソースは無いと言われる。ネットで探すと、こんなのがヒッ トした。

double
__atan2 (double y, double x)
{
  double z;
  if (__builtin_expect (x == 0.0 && y == 0.0, 0) && _LIB_VERSION == _SVID_)
    return __kernel_standard (y, x, 3); /* atan2(+-0,+-0) */
  z = __ieee754_atan2 (y, x);
  if (__glibc_unlikely (z == 0.0 && y != 0.0 && isfinite (x)))
    __set_errno (ERANGE);
  return z;
}
libm_alias_double (__atan2, atan2)

ふーん、_ieee754atan2 が本体なんか。更に探すと、 eatan2.c なんてのが出てきた。これが貧乏人御用達のFPUのシュミレータか。 なんか、パイが潜んでいるな。

awkの最高峰 GNU awk

だって、こんなのがサポートされてるからね。

14 Debugging awk Programs

[sakae@arch z]$ awk -D -f sel.awk LOG
gawk> list
1       BEGIN { file=""; fn=""; sym="" }
2        /: in function /           { fn = $5 }
3        /: undefined reference to/ { p = index($2, ":");
4                                     file = substr($2, 0, p - 1);
5                                     sym = $6;
6                                     printf "%16s  %-16s  %s\n", file,fn,sym }

これ、前回やったリンカー・エラーログを見易くするやつね。そのスクリプト をdebug option付きで起動してみた。listはdebuggerのコマンドの一つね。

gawk> help
backtrace:
        backtrace [N] - print trace of all or N innermost (outermost if N < 0) frames
break:
        break [[filename:]N|function] - set breakpoint at the specified location
 :
where:
        where [N] - (same as backtrace) print trace of all or N innermost (outermost if N < 0) frames

ってな具合に、コマンドを紹介してくれる。

gawk> b 6
Breakpoint 1 set at file `sel.awk', line 6
gawk> r
Starting program:
Stopping in Rule ...
Breakpoint 1, main() at `sel.awk':6
6                                     printf "%16s  %-16s  %s\n", file,fn,sym }
gawk> p $0
$0 = "minigzip.c:(.text+0xb5): undefined reference to `gzwrite'"
gawk> p NR
NR = 2

普通に組み込み変数にアクセス。

gawk> n
      minigzip.c  `gz_compress':    `gzerror'
2        /: in function /           { fn = $5 }
gawk>
3        /: undefined reference to/ { p = index($2, ":");
gawk>
4                                     file = substr($2, 0, p - 1);
gawk>
5                                     sym = $6;
gawk> p $0
$0 = "/usr/bin/ld: minigzip.c:(.text+0xfd): undefined reference to `gzclose'"

次、次も勿論OK.

gawk> p ENVIRON["TERM"]
ENVIRON["TERM"] = "tmux-256color"
gawk> p @ENVIRON
ENVIRON["AWKLIBPATH"] = "/usr/lib/gawk"
ENVIRON["AWKPATH"] = ".:/usr/share/awk"
ENVIRON["DBUS_SESSION_BUS_ADDRESS"] = "unix:path=/run/user/1000/bus"
 :

環境変数だってawkが得意な連想配列からアクセスできる。

tcp connection by gawk

networking features in GNU Awk (gawk) version 4.0 and later.

[sakae@fb /tmp/gawk-5.3.0]$ cat tcp.awk
BEGIN {
  RS = ORS = "\r\n"
  HttpService = "/inet/tcp/0/180.222.119.247/80"
  print "GET http://www.yahoo.com"     |& HttpService
  while ((HttpService |& getline) > 0)
     print $0
  close(HttpService)
}

180.222.119.247の部分は、あちらのヤホーさんのアドレスね。 HTTP/1.0 400 Invalid HTTP Request な返答だったけど、pingレベルで、意思 疎通が出来ました。世間はすっかりhttpsだからなあ、諦めろ。

最初この /inet/tcp/0/IP/port の構文を見た時、非常に違和感があった。で も冷静に考えたら、これが本来の姿だと考え直した。だって、unixでネットを 使う時だけ、特別な作法がある。ソケットをオープンして、バインドしてから リッスンなんて、後付けの方法だもの。

その点gawk式は、接続情報をデバイスに見立てていて統一感があるぞ。 open("/home/sakae/hoge.c","r")とかdd if=/dev/zero .. みたいなのと同一 のように見える。

ネットワークがオープン出来たら、リクエストをパイプ経由で書き込む。レス ポンスは、ネットワーク・デバイスからパイプを使って読み込み。とっても素 直なコードを実現してる。まさにplan 9的な、近未来のインターフェースだ。

どんな風に実現してるかソース参照。io.c

#ifdef HAVE_SOCKETS

/* socketopen --- open a socket and set it into connected state */

static int
socketopen(int family, int type, const char *localpname,
        const char *remotepname, const char *remotehostname, bool *hard_error)
{

こんな感じで5個のパラメーターを引数にして、ネットワークをオープンして る。なお、この、io.cでは、普通のファイルも扱っているね。

例をちょっと変更してみた。

BEGIN {
  RS = ORS = "\r\n"
  HttpService = "/inet/tcp/0/localhost/8080"
  print "GET / HTTP/1.0\n"     |& HttpService
  while ((HttpService |& getline) > 0)
     print $0
  close(HttpService)
}

これで、パイソン作のサーバーに(python3 -m http.server 8080)接続。

[sakae@arch z]$ awk -f tcp.awk
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.11.5
Date: Sat, 18 Nov 2023 00:28:05 GMT
Content-type: text/html; charset=utf-8
Content-Length: 226

<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Directory listing for /</title>
</head>
<body>
<h1>Directory listing for /</h1>
<hr>
<ul>
<li><a href="tcp.awk">tcp.awk</a></li>
</ul>
<hr>
</body>
</html>

リクエストの行の後に、空行が必要だぞ。忘れるな(オイラーは忘れていて、 レスポンスが返らず悩んだのは秘密だ)。


This year's Index

Home