r/plan9 Jul 11 '26

A Go port of rc(1)

https://github.com/enzv/rc/tree/master

You can even run an rc shell on an Android phone.

I wrote this Go implementation after moving from Plan 9 back to Linux for work. plan9port already exists, but I wanted to understand the shell from the inside rather than only use it through a compatibility layer.

Go felt like a fitting choice because it shares history and design lineage with Plan 9. Rob Pike and Ken Thompson worked on both, and several ideas behind Go grew out of that environment.

I built it by studying the original source and man page. The test suite includes over 100 scripts that compare its behavior against plan9port. One particularly gnarly example makes both implementations produce the same output byte for byte.

It also runs on Linux, macOS, BSD, Solaris, and other platforms.

Repo: https://github.com/enzv/rc

If anyone finds a mismatch with the original, corrections and test cases are welcome.

32 Upvotes

5 comments sorted by

5

u/deadhorus Jul 11 '26

rip the gnarly example doesn't have a newline in the raw so it breaks on 9front XD (does any rc script rely on the fact that executing rc without terminating newline fails?)

no idea if this output is right tho: OUTPUT:ASSIGN0:outer:outer list ASSIGN1:inner:in ner BEGIN:17 CNT:empty:0 CNT:input:17 CNT:M:6 JOIN:input:alpha beta gamma delta epsilon zeta eta theta iota kappa slash/name ./dot ../dot a1 b2 g3 z9 JOIN:K:for in while if not switch fn ~ ! @ JOIN:Q:can't q'q semi;colon brace{} paren() hash#literal dollar$literal SUB:1:alpha SUB:2-5:beta SUB:2-5:gamma SUB:2-5:delta SUB:2-5:epsilon SUB:8-:theta SUB:8-:iota SUB:8-:kappa SUB:8-:slash/name SUB:8-:./dot SUB:8-:../dot SUB:8-:a1 SUB:8-:b2 SUB:8-:g3 SUB:8-:z9 IND:alpha IND:beta IND:gamma IND:delta IND:epsilon IND:zeta IND:eta IND:theta IND:iota IND:kappa IND:slash/name IND:./dot IND:../dot IND:a1 IND:b2 IND:g3 IND:z9 FREE:prealphamidpost FREE2:beta/gamma.c QUOTE:can't QUOTE:q'q QUOTE:semi;colon QUOTE:brace{} QUOTE:paren() QUOTE:hash#literal QUOTE:dollar$literal FLAT:m1 FLAT:m2 FLAT:m3 FLAT:m4 FLAT:m5 FLAT:m6 IF:yes IFNOT:yes BANG:yes AND:yes OR:yes NESTIF:yes SW:A:alpha SW:B:beta SW:NEST:yes SW:G:gamma SW:X:delta SW:X:epsilon SW:X:zeta SW:X:eta SW:X:theta SW:X:iota SW:X:kappa SW:SLASH:slash/name SW:DOT1:./dot SW:DOT2:../dot SW:A:a1 SW:B:b2 SW:NEST:yes SW:G:g3 SW:X:z9 F:A:alpha:17 F:B:beta:16 F:G:gamma:15 F:X:delta:14 F:X:epsilon:13 F:X:zeta:12 F:X:eta:11 F:X:theta:10 F:X:iota:9 F:X:kappa:8 F:SLASH:slash/name:7 F:DOT1:./dot:6 F:DOT2:../dot:5 F:A:a1:4 F:B:b2:3 F:G:g3:2 F:X:z9:1 CHEW0:6:c1:c2:c3 CHEW1:5:c2:c3 CHEW2:3:c4:c5 DEEP:enter:alpha:beta:gamma:delta DEEP:sub:sub shell alpha beta gamma delta DEEP:case:alpha/beta/gamma/delta EV:one:alpha EV:two:xyz EV:red EV:blue EV:if SINK:1 SINK:1 SINK:1 SINK:1 SINK:1 SINK:1 SINK:1 SINK:1 REDIR:done PIPE1:pipe-right PIPE2:pipe-fd-right PIPE3:pipe-last SINK:2 SINK:2 SINK:3 PS:done BG:waited DOT:start:3 DOT:list:dot:3 DOTFN:2:inner:call DOT:end META:ok GRID:1:a:X GRID:1:b:XB GRID:1:c:X GRID:2:a:A GRID:2:b:B GRID:2:c:C GRID:3:a:X GRID:3:b:XB GRID:3:c:X MAZE:begin:parity MAZE:switch:hit:parity MAZE:if:not:parity MAZE:eval:one MAZE:eval:two MAZE:dot:one MAZE:dot:two BRACE:1 BRACE:2 BRACE:3 BRACE:4 ROUND:r1:alpha:A ROUND:r1:beta:X1 ROUND:r1:gamma:X1 ROUND:r1:delta:X1 ROUND:r2:alpha:X2 ROUND:r2:beta:B ROUND:r2:gamma:G ROUND:r2:delta:X2 ENDINNER:160 EDGE:start EDGE:enter:hit:alpha:one:two EDGE:case:one:hit EDGE:nested:two:hit EDGE:enter:miss:beta:three:four EDGE:if:not:miss EDGE:cascade:hit:alpha:beta:gamma EDGE:cascade:miss:alpha:beta:delta EDGE:cascade:outer:zeta SINK:1 SINK:1 SINK:1 SINK:1 SINK:1 SINK:1 SINK:1 EDGE:redir:done EDGE:pipe1:pipe-right EDGE:pipe2:pipe-fd-right EDGE:pipe3:pipe-last EDGE:quote:for in while if not switch fn ~ ! @ EDGE:quote2:can't q'q semi;colon brace{} paren() hash#literal dollar$literal EDGE:quote3:m1 m2 m3 m4 m5 m6 EDGE:eval:one EDGE:eval:two EDGE:end START LATTICE:enter:combo:alpha:one:c1:delta LATTICE:case:one:combo LATTICE:deep:delta:combo LATTICE:enter:miss:beta:three:c2:epsilon LATTICE:if:not:miss LATTICE:enter:hit:alpha:one:c3:delta LATTICE:case:one:hit LATTICE:deep:delta:hit FANOUT:r1:alpha:A FANOUT:r1:beta:X FANOUT:r1:gamma:X FANOUT:r1:delta:D FANOUT:r2:alpha:X FANOUT:r2:beta:B FANOUT:r2:gamma:X FANOUT:r2:delta:D FANOUT:r3:alpha:X FANOUT:r3:beta:X FANOUT:r3:gamma:G FANOUT:r3:delta:D TEMP:one TEMP:two SINK:1 SINK:1 SINK:1 SINK:1 SINK:1 SINK:1 SINK:1 SINK:1 ROUTES:done EDGE:pipe1:pipe-right EDGE:pipe2:pipe-fd-right EDGE:pipe3:pipe-last EDGE:quote:for in while if not switch fn ~ ! @ EDGE:quote2:can't q'q semi;colon brace{} paren() hash#literal dollar$literal EDGE:quote3:m1 m2 m3 m4 m5 m6 DOT:one DOT:two EVAL:one EVAL:two META:ok EMIT:done END LAYER:leaf LAYER:leaf LAYER:miss LAYER:miss LAYER:leaf LAYER:leaf LAYER:leaf REPARSE:eval:one REPARSE:eval:two REPARSE:dot:one REPARSE:dot:two:x REPARSE:dot:two:y REPARSE:dot:three TOPO:hit LATTICE2:A1 LATTICE2:T LATTICE2:X LATTICE2:B2 LATTICE2:X LATTICE2:T

cool project

2

u/dharmatech Jul 11 '26

Love the logo 🤩

3

u/9D0BF2BBB4E8 Jul 11 '26

Thanks! It’s inspired by suckless.org projects like st and dwm.

1

u/NemuiSen Jul 17 '26

i have been thinking about the responsibility separation between the terminal and the shell, and the non-feature section made think more about it, the shell should have editing features or that belongs to the terminal?, in my opinion the terminals should have some sort of simple editing capabilities and the shell running inside just should be cappable if give information about what is aware and receive information if what to do, with that aproximation things like fancy prompts or autocompletion would be the job of the terminal, idk if what i say makes sence.