Skip to content

Instantly share code, notes, and snippets.

@spencer-p
Last active October 21, 2023 15:18
Show Gist options
  • Save spencer-p/b16bfd6d949a00e63c6bf248ca8f0451 to your computer and use it in GitHub Desktop.
Save spencer-p/b16bfd6d949a00e63c6bf248ca8f0451 to your computer and use it in GitHub Desktop.
Coding golf shell!

Coding Golf Shell

This is a fully functional UNIX shell implemented in minimal bytes. You may have to disable some compiler warnings to compile it.

Feature Set

  • Execution of single commands as expected
  • Piping commands arbitrarily with |
  • Redirecting both standard input and output with < and > respectively
  • Semicolons to terminate commands and join multiple commands on a single line
  • Shell builtins cd and exit

Anti-feature set

  • Anything that might make this turing complete
  • Any variety of feedback in the form of errors

Why and How?

It's fun to test the limits of C programming. This could likely be smaller, but a functional shell program that could fit on a business card or be encoded into a mid-size QR code is quite a feat.

The core of this shell is simplistic interpret rolled into a heavily stateful parsing state machine. There is string processing and a few global variables to manage special cases for the command as well as parser state.

The core is the string processing. There is a main buffer of chars that holds the input text, then a buffer of char* that points into the text buffer, and then a further char** buffer that points into the previous buffer. Appropriate state transitions are made for each character of the input buffer such that this array of array of array of chars is a list of commands comprised of a list of strings.

Finally, with all this information, the execution of the command is not difficult. The execution steps are not difficult to read, though trying to reason about the program as whole is not easy, even with this information.

#include <ctype.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#define cl(x) if(x!=0&&x!=1)close(x);
#define m(x) memset(x,0,sizeof(x));
#define rw(x) while(isspace((c=getchar())));i=0;ungetc(c,stdin);while(!isspace(\
c=getchar())){x[i++]=c;}x[i]='\0';ungetc(c,stdin);
int main(){char b[BUFSIZ],*cb[BUFSIZ],**eb[BUFSIZ],in[BUFSIZ],ou[BUFSIZ];char c,
**cmd=cb,***e=eb;int bi=0,p,i,pf[2],cin,cout,next,ws[BUFSIZ],wsi=-1,o,d=1;run:m(
b);m(cb);m(eb);bi=0;*in='\0';*ou='\0';cmd=cb,e=eb,o=0;if(d){printf("$ ");fsync(1
);d=0;};while((c=getchar())!=EOF){switch(c){case' ':bi++;cmd++;break;case'|':bi
++;cmd++;e++;break;case'>':if((c=getchar())=='>'){o=O_APPEND;}else{ungetc(c,
stdin);o=O_TRUNC;}rw(ou);break;case'<':rw(in);break;case'\n':d=1;case';':cin=*in
?open(in,O_RDONLY):0;for(i=0;eb[i];i++){if(!strcmp(eb[i][0],"exit")){exit(0);}if
(!strcmp(eb[i][0],"cd")&&eb[i][1]){chdir(eb[i][1]);goto run;}if(eb[i+1]){pipe(pf
);next=*pf;cout=pf[1];}else{next=0;cout=*ou?open(ou,O_WRONLY|O_CREAT|o,420):1;}
if((p=fork())){cl(cin);cl(cout);cin=next;ws[++wsi]=p;}else{dup2(cin,0);dup2(cout
,1);cl(next);execvp(eb[i][0],eb[i]);exit(1);}}while(wsi>=0){waitpid(ws[wsi--],0,
0);}goto run;default:if(!*cmd){*cmd=b+bi;}if(!*e){*e=cmd;}b[bi]=c;bi++;break;}}}
/* This is the exact same code as above run through clang-format */
#include <ctype.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#define cl(x) \
if (x != 0 && x != 1) \
close(x);
#define m(x) memset(x, 0, sizeof(x));
#define rw(x) \
while (isspace((c = getchar()))) \
; \
i = 0; \
ungetc(c, stdin); \
while (!isspace(c = getchar())) { \
x[i++] = c; \
} \
x[i] = '\0'; \
ungetc(c, stdin);
int main() {
char b[BUFSIZ], *cb[BUFSIZ], **eb[BUFSIZ], in[BUFSIZ], ou[BUFSIZ];
char c, **cmd = cb, ***e = eb;
int bi = 0, p, i, pf[2], cin, cout, next, ws[BUFSIZ], wsi = -1, o, d = 1;
run:
m(b);
m(cb);
m(eb);
bi = 0;
*in = '\0';
*ou = '\0';
cmd = cb, e = eb, o = 0;
if (d) {
printf("$ ");
fsync(1);
d = 0;
};
while ((c = getchar()) != EOF) {
switch (c) {
case ' ':
bi++;
cmd++;
break;
case '|':
bi++;
cmd++;
e++;
break;
case '>':
if ((c = getchar()) == '>') {
o = O_APPEND;
} else {
ungetc(c, stdin);
o = O_TRUNC;
}
rw(ou);
break;
case '<':
rw(in);
break;
case '\n':
d = 1;
case ';':
cin = *in ? open(in, O_RDONLY) : 0;
for (i = 0; eb[i]; i++) {
if (!strcmp(eb[i][0], "exit")) {
exit(0);
}
if (!strcmp(eb[i][0], "cd") && eb[i][1]) {
chdir(eb[i][1]);
goto run;
}
if (eb[i + 1]) {
pipe(pf);
next = *pf;
cout = pf[1];
} else {
next = 0;
cout = *ou ? open(ou, O_WRONLY | O_CREAT | o, 420) : 1;
}
if ((p = fork())) {
cl(cin);
cl(cout);
cin = next;
ws[++wsi] = p;
} else {
dup2(cin, 0);
dup2(cout, 1);
cl(next);
execvp(eb[i][0], eb[i]);
exit(1);
}
}
while (wsi >= 0) {
waitpid(ws[wsi--], 0, 0);
}
goto run;
default:
if (!*cmd) {
*cmd = b + bi;
}
if (!*e) {
*e = cmd;
}
b[bi] = c;
bi++;
break;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment