Yash: Yet another shell

This is a simple thought experiment: How small can we make a usable *NIX command shell? This program, only 127 lines long (complete with documentation), is a very basic *NIX command shell. It can be downloaded here

Since the program is so small, I am including its source below. Note that there are ways to make this smaller by, say, using strtok to parse the line, and strcmp to handle the "cd" command. However, this code style makes extending the shell easier; the smallest shell that one can make is actually main(){char i[99];for(;;){gets(i);system(i);}}

Without further ado, the code:

/* Placed in the public domain 2009 by Sam Trenholme */

/* Yash: A usable command shell in under 4k (yet another shell)
 *
 * This is a simple thought experiment: How small can we make a *NIX command
 * line shell?  This is as simple as it gets; it will run a command typed
 * in if it's an external command like "ls" or "cc", with arguments separated
 * by space; if one types in "-cd {directory}", it will go to that directory.
 *
 * CNTL-C exits the shell
 *
 * BUGS: CNTL-D reruns the previous command instead of exiting the shell.
 */

#define LINEMAX 80
#define ARGMAX 16

#include <stdio.h>
#include <unistd.h>

/* Given a pointer to arguments, destroy the string */
void yash_zap_args(char **zap) {
	int a = 0;

	if(zap == 0) {
		return;
	}

	for(a = 0; a < ARGMAX; a++) {
		if(zap[a] != 0) {
			free(zap[a]);
			zap[a] = 0;
		}
	}

	free(zap);
}

/* Given a line separated by whitespace, return an array of strings
 * of the individual arguments */

char **yash_args(char *in) {
	char **out = 0;
	int a = 0, b = 0;

	/* Sanity checks */
	if(in == 0) {
		return 0;
	}

	out = malloc(ARGMAX * sizeof(char *));
	for(a = 0; a < ARGMAX; a++) {
		out[a] = 0;
	}
	a = 0;

	if(out == 0) {
		return 0;
	}

	while(*in != 0) {
		if(*in != ' ' && *in != '\n') {
			if((out[a] = malloc(LINEMAX)) == 0) {
				goto catch_yash_args;
			}
			b = 0;
			while(b < LINEMAX - 2 && *in != ' ' && *in != 0 &&
                              *in != '\n') {
				out[a][b] = *in;
				b++;
				in++;
			}
			out[a][b] = 0;
			a++;
			if(a >= ARGMAX) {
				goto catch_yash_args;
			}
		}
		in++;
	}	

	return out;
		
catch_yash_args:
	yash_zap_args(out);
	return 0;
}

/* Meta commands for yash; right now this is just the cd command */ 
void yash_meta(char **args) {
	if(args == 0 || args[0] == 0) {
		return;
	}

	if(args[0][1] == 'c' && args[0][2] == 'd') {
		if(args[1] == 0) {
			chdir("/"); /* Should be $HOME, isn't */
		} else {
			chdir(args[1]);
		}
		return;
	}
}
		
main() {
	char in[LINEMAX], **args = 0;

	for(;;) {
		printf("$ ");
		fgets(in,LINEMAX,stdin);
		args = yash_args(in);
		if(args != 0 && args[0] != 0) {
			if(*args[0] == '-') {
				yash_meta(args);
			}
			else if(fork()) {
				wait(0);
			} else {
				execvp(args[0],args);
				printf("Command not found\n");
				exit(-1);
			}
		}		
		yash_zap_args(args);
	}
}