I wrote a shell to test my understanding of processes etc. It is not supposed to comply with POSIX or anything, just to allow people to run simple commands with simple arguments.
#include <stdio.h> /* printf, fprintf */ #include <stdlib.h> /* exit */ #include <string.h> /* strtok */ #include <unistd.h> /* fork, execvp */ #include <sys/wait.h> /* waitpid */ #include <sys/types.h> /* waitpid */ /* * This program is a simple, first-level shell for testing and development purposes. * It is called rsh: the Recovery SHell because it can be used for recovery as it has * no dependencies apart from the C standard library and is very small and simple. * This shell does not follow POSIX or anything else: it is purely for executing * commands with simple arguments (ie no quote escape, backslash escape, etc) */ /* * Function Return Values: * All functions in this program return 0 on success and -1 on failure, unless they * return useful information, in which the return values will be documented in a * comment at the top of the function. With the exception of builtins. Builtin * commands return 0 for success and 1 for not successful, like the real commands. */ /* A list of builtin functions here */ char *builtins[] = { "exit", "cd", "help" }; /* * Return the number of builtin commands so we can avoid hard-coding random magic * numbers and other annoyances everywhere. */ int rsh_num_builtins(void) { return sizeof(builtins) / sizeof(char *); } /* * This builtin function exits the shell. */ int rsh_exit(char **args) { exit(EXIT_SUCCESS); } /* * This builtin function changes the directory of the shell process, like the cd * in normal shells. */ int rsh_cd(char **args) { if (args[1] == NULL) { fprintf(stderr, "rsh: expected argument to cd\n"); return 0; } else if (chdir(args[1]) == -1) { fprintf(stderr, "rsh: can't cd to %s\n", args[1]); return 1; } return 0; } /* * Print out a help message about rsh */ int rsh_help(char **args) { puts("rsh: recovery shell"); puts(""); puts("rsh is a very small and simple shell that is used for system recovery purposes."); puts("It is not compliant with anything and does not allow escaping, quoting, sourcing"); puts("backslashing or anything else. It is scriptable, but only in such a way that lets"); puts("you run commands with simple options and arguments: nothing else!"); puts(""); puts("List of builtin commands:"); puts("exit cd help"); puts("All those builtins do exactly what you expect them to do, so no documentation is"); puts("needed, hopefully."); return 0; } int (*builtin_func[]) (char **) = { &rsh_exit, &rsh_cd, &rsh_help }; int rsh_external_execute(char **args) { pid_t pid; pid_t wpid; int status; /* fork off a new process */ pid = fork(); if (pid == 0) { /* in the child process. Note that this does not exit the parent * in an error, with the exit() call. */ if (execvp(args[0], args) == -1) { fprintf(stderr, "rsh: cannot exec\n"); exit(EXIT_FAILURE); } } else if (pid < 0) { /* error forking */ fprintf(stderr, "rsh: cannot fork\n"); return -1; } else { /* parent process */ do { wpid = waitpid(pid, &status, WUNTRACED); } while (!WIFEXITED(status) && !WIFSIGNALED(status)); } return 0; } /* * This function handles execution of code and builtins. It takes the argument argv * which is the argv of the program about to be executed. The fork-exec process * takes place in the function rsh_external_execute(). */ int rsh_execute(char **args) { int i; if (args[0] == NULL) { /* An empty command was entered */ return 1; } for (i = 0; i < rsh_num_builtins(); i++) { if (strcmp(args[0], builtins[i]) == 0) { return (*builtin_func[i])(args); } } return rsh_external_execute(args); } /* * Read a line from stdin and return it. Uses getline() for simplicity, it was added * to POSIX recently and was originally a GNU extension to the C library. */ char *rsh_readline(void) { char *lineptr = NULL; size_t n = 0; if (getline(&lineptr, &n, stdin) == -1) { fprintf(stderr, "rsh: input error\n"); return NULL; } else { return lineptr; } } char **rsh_tokenise(char *line) { const int bufsize_orig = 1; int bufsize = bufsize_orig; const char *tok_delim = " \t\r\n\a"; int position = 0; char **tokens = malloc(bufsize * sizeof(char*)); char *token; if (!tokens) { fprintf(stderr, "rsh: allocation error\n"); exit(EXIT_FAILURE); } token = strtok(line, tok_delim); while (token != NULL) { tokens[position] = token; position++; if (position >= bufsize) { bufsize += bufsize_orig; tokens = realloc(tokens, bufsize * sizeof(char*)); if (!tokens) { fprintf(stderr, "rsh: allocation error\n"); exit(EXIT_FAILURE); } } token = strtok(NULL, tok_delim); } tokens[position] = NULL; return tokens; } /* * The main function just drops into an infinite loop of Read, Parse, Execute. * Read = use getline() to get a line from stdin, * Parse = tokenise with strtok(), * Execute = use execvp() to execute programs or execute builtin commands. */ int main(void) { char *line; char **args; /* * This is the infinite loop of the shell. */ for (;;) { /* Check UID and print the correct prompt out */ if (geteuid() == 0) { printf("rsh # "); } else { printf("rsh $ "); } line = rsh_readline(); args = rsh_tokenise(line); rsh_execute(args); /* Free up the unused memory */ free(line); free(args); } } Any improvements would be welcome, especially stuff about buffer overflows and memory leaks because I didn't check for many of those.