MacOS XでThompson Shellを動かす

Thompson Shellといっても耳馴染みない人も多いかと思うが、Bourne Shellになる前の由緒正しいUNIXシェルである。Thompsonとは、もちろんKen Thompsonのこと。Wikipediaの「Bourne Shellのページによると、

Bourne Shell(ボーンシェル)は、Unix Version 7のシェルであり、それまでのThompson Shellを置き換えた(いずれもコマンド名は sh である)。

その後、(後にSun Microsystemsを興すことになる)Bill JoyはThompson ShellからC Shellを派生させた。そして、Bourne ShellやC Shellの成果を反映させる形で、UNIX V10に登場したのがrcである。

rc はベル研究所の Tom Duff が Version 10 Unix(Plan 9 の後継)で Bourne sh の代替として開発したものである。Plan 9 や Inferno でのデフォールトシェルでもある。UNIX系OSにも移植されている(Plan 9 from User Space)。

温故知新ということで(?)、UNIX V6のThompson ShellをMacOS Xに移植してみた。1ファイルの非常にシンプルな作りである。cdはbuiltinじゃなくて、chdirというbuiltinが提供されているのかぁ。

とりあえず動くようになったのでパッチを曝してみる。

まず注意が必要なのは、ポータブルCコンパイラ以前のPDP-11 Cコンパイラなので、Cの構文が微妙に違うこと。"=|"や"=+"など演算子が=の前置じゃなくて後置になる。また、setexit/resetって関数があるけど、これはsetjmp/longjmpに相当するようだ。ジャンプバッファはグローバルに一つという感じに見受けられる。あと、intは16ビットなので、32ビットの時間情報を扱うのに、int[2]を使っている部分は適当に直したが、ちゃんと動作は確認していない。

--- sh.orig.c   2008-09-12 20:02:12.000000000 +0900
+++ sh.c        2008-09-12 20:43:39.000000000 +0900
@@ -1,5 +1,8 @@
 /*
+ * Thompson shell (UNIX V6)
  */
+#include <stdio.h>
+#include <stdlib.h>
 
 #define        INTR    2
 #define        QUIT    3
@@ -49,7 +52,7 @@
 char   *arginp;
 int    onelflg;
 
-char   *mesg[] {
+char   *mesg[] = {
        0,
        "Hangup",
        0,
@@ -73,21 +76,28 @@
 };
 
 struct stime {
-       int proct[2];
-       int cputim[2];
-       int systim[2];
+       int proct;
+       int cputim;
+       int systim;
 } timeb;
 
 char   line[LINSIZ];
 char   *args[ARGSIZ];
 int    trebuf[TRESIZ];
 
+/* Emulate setexit/reset by using setjmp/longjmp. */
+#include <setjmp.h>
+jmp_buf jbuf;
+#define setexit() setjmp(jbuf)
+#define reset() longjmp(jbuf, 1)
+
 main(c, av)
 int c;
 char **av;
 {
        register f;
        register char *acname, **v;
+       div_t a;
 
        for(f=2; f<15; f++)
                close(f);
@@ -95,7 +105,8 @@
                close(f);
        dolc = getpid();
        for(f=4; f>=0; f--) {
-               dolc = ldiv(0, dolc, 10);
+               a = div(dolc, 10);
+               dolc = a.quot;
                pidp[f] = ldivr+'0';
        }
        v = av;
@@ -132,7 +143,7 @@
 loop:
        if(promp != 0)
                prs(promp);
-       peekc = getc();
+       peekc = xgetc();
        main1();
        goto loop;
 }
@@ -174,7 +185,7 @@
        *argp++ = linep;
 
 loop:
-       switch(c = getc()) {
+       switch(c = xgetc()) {
 
        case ' ':
        case '\t':
@@ -211,7 +222,7 @@
 
 pack:
        for(;;) {
-               c = getc();
+               c = xgetc();
                if(any(c, " '\"\t;&<>()|^\n")) {
                        peekc = c;
                        if(any(c, "\"'"))
@@ -229,7 +240,7 @@
        register *t;
 
        t = treep;
-       treep =+ n;
+       treep += n;
        if (treep>treeend) {
                prs("Command line overflow\n");
                error++;
@@ -238,7 +249,7 @@
        return(t);
 }
 
-getc()
+xgetc()
 {
        register char c;
 
@@ -248,17 +259,17 @@
                return(c);
        }
        if(argp > eargp) {
-               argp =- 10;
-               while((c=getc()) != '\n');
-               argp =+ 10;
+               argp -= 10;
+               while((c=xgetc()) != '\n');
+               argp += 10;
                err("Too many args");
                gflg++;
                return(c);
        }
        if(linep > elinep) {
-               linep =- 10;
-               while((c=getc()) != '\n');
-               linep =+ 10;
+               linep -= 10;
+               while((c=xgetc()) != '\n');
+               linep += 10;
                err("Too many characters");
                gflg++;
                return(c);
@@ -299,7 +310,7 @@
 
        if (arginp) {
                if (arginp == 1)
-                       exit();
+                       exit(1);
                if ((c = *arginp++) == 0) {
                        arginp = 1;
                        c = '\n';
@@ -307,9 +318,9 @@
                return(c);
        }
        if (onelflg==1)
-               exit();
+               exit(1);
        if(read(0, &cc, 1) != 1)
-               exit();
+               exit(1);
        if (cc=='\n' && onelflg)
                onelflg--;
        return(cc);
@@ -372,7 +383,7 @@
                        t[DFLG] = 0;
                        if(l == '&') {
                                t1 = t[DLEF];
-                               t1[DFLG] =| FAND|FPRS|FINT;
+                               t1[DFLG] |= FAND|FPRS|FINT;
                        }
                        t[DRIT] = syntax(p+1, p2);
                        return(t);
@@ -437,7 +448,7 @@
 
        flg = 0;
        if(**p2 == ')')
-               flg =| FPAR;
+               flg |= FPAR;
        lp = 0;
        rp = 0;
        i = 0;
@@ -465,7 +476,7 @@
        case '>':
                p++;
                if(p!=p2 && **p=='>')
-                       flg =| FCAT; else
+                       flg |= FCAT; else
                        p--;
 
        case '<':
@@ -628,14 +639,14 @@
                        if(i < 0) {
                                prs(t[DLEF]);
                                err(": cannot open");
-                               exit();
+                               exit(1);
                        }
                }
                if(t[DRIT] != 0) {
                        if((f&FCAT) != 0) {
                                i = open(t[DRIT], 1);
                                if(i >= 0) {
-                                       seek(i, 0, 2);
+                                       lseek(i, 0, 2);
                                        goto f1;
                                }
                        }
@@ -643,7 +654,7 @@
                        if(i < 0) {
                                prs(t[DRIT]);
                                err(": cannot create");
-                               exit();
+                               exit(1);
                        }
                f1:
                        close(1);
@@ -672,9 +683,9 @@
                }
                if(t[DTYP] == TPAR) {
                        if(t1 = t[DSPR])
-                               t1[DFLG] =| f&FINT;
+                               t1[DFLG] |= f&FINT;
                        execute(t1);
-                       exit();
+                       exit(1);
                }
                close(acctf);
                gflg = 0;
@@ -683,7 +694,7 @@
                        t[DSPR] = "/etc/glob";
                        execv(t[DSPR], t+DSPR);
                        prs("glob: cannot execute\n");
-                       exit();
+                       exit(1);
                }
                scan(t, &trim);
                *linep = 0;
@@ -698,26 +709,26 @@
                texec(linep, t);
                prs(t[DCOM]);
                err(": not found");
-               exit();
+               exit(1);
 
        case TFIL:
                f = t[DFLG];
                pipe(pv);
                t1 = t[DLEF];
-               t1[DFLG] =| FPOU | (f&(FPIN|FINT|FPRS));
+               t1[DFLG] |= FPOU | (f&(FPIN|FINT|FPRS));
                execute(t1, pf1, pv);
                t1 = t[DRIT];
-               t1[DFLG] =| FPIN | (f&(FPOU|FINT|FAND|FPRS));
+               t1[DFLG] |= FPIN | (f&(FPOU|FINT|FAND|FPRS));
                execute(t1, pv, pf2);
                return;
 
        case TLST:
                f = t[DFLG]&FINT;
                if(t1 = t[DLEF])
-                       t1[DFLG] =| f;
+                       t1[DFLG] |= f;
                execute(t1);
                if(t1 = t[DRIT])
-                       t1[DFLG] =| f;
+                       t1[DFLG] |= f;
                execute(t1);
                return;
 
@@ -738,12 +749,12 @@
                t[DSPR] = "/bin/sh";
                execv(t[DSPR], t+DSPR);
                prs("No shell!\n");
-               exit();
+               exit(1);
        }
        if (errno==ENOMEM) {
                prs(t[DCOM]);
                err(": too large");
-               exit();
+               exit(1);
        }
 }
 
@@ -754,8 +765,8 @@
        prs(s);
        prs("\n");
        if(promp == 0) {
-               seek(0, 0, 2);
-               exit();
+               lseek(0, 0, 2);
+               exit(1);
        }
 }
 
@@ -766,10 +777,10 @@
 
        s = as;
        while(*s)
-               putc(*s++);
+               xputc(*s++);
 }
 
-putc(c)
+xputc(c)
 {
 
        write(2, &c, 1);
@@ -778,11 +789,12 @@
 prn(n)
 int n;
 {
-       register a;
+       register div_t a;
 
-       if(a=ldiv(0,n,10))
+       a = div(n,10);
+       if(a.quot)
                prn(a);
-       putc(lrem(0,n,10)+'0');
+       xputc(a.rem+'0');
 }
 
 any(c, as)
@@ -862,10 +874,10 @@
                char cname[14];
                char shf;
                char uid;
-               int datet[2];
-               int realt[2];
-               int bcput[2];
-               int bsyst[2];
+               int datet;
+               int realt;
+               int bcput;
+               int bsyst;
        } tbuf;
        register i;
        register char *np, *s;
@@ -873,9 +885,9 @@
        s = as;
        times(&timbuf);
        time(timbuf.proct);
-       lsub(tbuf.realt, timbuf.proct, timeb.proct);
-       lsub(tbuf.bcput, timbuf.cputim, timeb.cputim);
-       lsub(tbuf.bsyst, timbuf.systim, timeb.systim);
+       tbuf.realt = timbuf.proct - timeb.proct;
+       tbuf.bcput = timbuf.cputim - timeb.cputim;
+       tbuf.bsyst = timbuf.systim - timeb.systim;
        do {
                np = s;
                while (*s != '\0' && *s != '/')
@@ -886,12 +898,11 @@
                if (*np)
                        np++;
        }
-       tbuf.datet[0] = timbuf.proct[0];
-       tbuf.datet[1] = timbuf.proct[1];
+       tbuf.datet = timbuf.proct;
        tbuf.uid = uid;
        tbuf.shf = 0;
        if (promp==0)
                tbuf.shf = 1;
-       seek(acctf, 0, 2);
+       lseek(acctf, 0, 2);
        write(acctf, &tbuf, sizeof(tbuf));
 }

しかし思えば、30年以上前のプログラムがちょっとした修正で動くのだから、改めてC & UNIXの移植性の高さを感じる。

余談だが、Bourne Shellの特徴として、

Bourne Shell はまた、エラーメッセージのために 2番のファイル記述子を使うという規定を最初に採用したプログラムでもある。これにより、エラーメッセージをデータと分離してスクリプトで制御することが容易になった。

とある。確かに、Thompson shellはfd 2をまったくケアしていない。

昔のエントリを検索してみたら、「Bourne shell」に関するものがあった。そう、Bourne shellのコードは無理し過ぎというか、はっきり言って読みにくい。ちなみに、OpenSolarisのBourne shell(Hairloom Bourne Shell)はALGOL 68風のコードではない。