https://github.com/michaelsproul/simple-shell
A simple shell written in C
https://github.com/michaelsproul/simple-shell
shell
Last synced: about 2 months ago
JSON representation
A simple shell written in C
- Host: GitHub
- URL: https://github.com/michaelsproul/simple-shell
- Owner: michaelsproul
- Created: 2016-01-09T23:11:36.000Z (over 10 years ago)
- Default Branch: master
- Last Pushed: 2016-01-09T23:11:52.000Z (over 10 years ago)
- Last Synced: 2026-04-24T08:42:29.179Z (2 months ago)
- Topics: shell
- Language: C
- Homepage:
- Size: 7.81 KB
- Stars: 0
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Simple Shell
A simple shell written in C.
## Features
* Unlimited pipes.
* Unlimited arguments.
* Sensible file redirection.
* Changing directories.
## Usage
```
$ make
$ ./shell
```

## High-Level Description
There is a central loop in `shell.c` that repeatedly fetches commands from standard input and executes them. A flex tokeniser provides a token stream to the `parse` function, which ensures the input is well formed and stores all relevant information in the `program_args` variable. If parsing is successful, the main function passes the details of the command to be executed to the execute function, which does all piping and forking.
## Desirable Properties
* Parsing is separated from execution. The execution stage can assume well-formed input, and treat program data structures as read-only.
* No memory leaks. Failure is bubbled up and carefully handled.
* Important forking and piping is contained in a very small section of code in `exec.c`.
* Failure to execute a command just results in the main execution loop continuing to the next bit of input. The shell is not programmed to exit unless instructed to by the user.
## Parsing Details
I decided to allow input of the following form:
1. One or more programs, each with zero or more arguments.
2. File redirection only where sensible. It doesn't make sense to read in a file on anything but the first command, nor does it make sense to redirect output to a file on anything but the last command.
3. No program arguments following a file redirection operation.
To achieve this I had to implement dynamic arrays with `void *` hackery.
The main parsing function takes a `char **** program_args`, which can be thought of as a pointer to a dynamic array of dynamic string arrays. There's an extra level of redirection so that it can be re-assigned if adding elements to the array causes a resize.
## Execution Details
To support unlimited piping my shell iterates through each program to be run and creates a pipe for its output. At each iteration a file descriptor pointing to the read-end of the previous program's pipe is held by the main process. This file descriptor is used to replace the standard input of newly spawned programs (using the standard `fork` and `exec` method). The only special cases are the first and last program, which need not be distinct, which interact with `stdin` (or an input file) and `stdout` (or an output file). This nice simplicity arises from the limitation on file redirection enforced by the parser. The tradeoff seems fair given that file redirection in the middle of a pipeline is fairly uncommon and can be done with `tee` anyway.
Once all of the children have been spawned, the main process waits for each of them in order. Initially I was just using `wait`, but switched to `waitpid` so that I could report the names of failed processes.
## Builtins
There are two shell builtins, `cd` and `exit`. Both must be run on their own without piping or file redirection. This decision was made to keep thing as simple as possible. The calling format for `cd` is:
```
cd
```
The directory argument *must* be provided, calling `cd` without an argument will not go to `~` as the shell has no concept of home directories.