A tiny, simple UNIX shell, with a (very) verbose mode. The verbose mode is designed to give the user a walkthrough of the shell program flow and reveal the low-level mechanisms used by the shell while carrying out the user's commands.
You might be asking yourself a very valid question:
Tinysh is obviously not meant for any serious shell work. However, running the shell in verbose mode, inspecting the code, and implementing your own features can serve as a useful pedagogical tool for learning the basic underpinnings of a simplified shell architecture. If you're in the midst of taking your first operating systems class, or if you have just started to learn how to use a UNIX shell and you're curious about what's under the hood, hopefully messing around with tinysh will help solidify some foundational concepts.
There are two easy ways to start using the shell.
- Download the tinysh executable from bin directory.
- There are three quick ways to run the shell:
- Run
$ /path/to/file/tinysh
- or, add
/path/to/file/
to your path and run
$ tinysh
- or, navigate to the download directory where tinysh is located
and run$ cd /path/to/file
$ ./tinysh
- Clone or download this repository.
- Navigate to the repository directory.
$ cd /path/to/repository/tinysh
- Clean the repository of temporary files, object files, and executables.
$ make clean
- Compile tinysh.
$ make
- Run tinysh.
$ ./bin/tinysh
The shell has the following options:
tinysh [-p|--path file] [-h|--help] [-v|--verbose]
-p file, --path file
- Instead of using the path defined by your environment, use the paths defined in the file whose
name is the string
file
. tinysh assumes thatfile
consists of paths to commands and programs that the user wishes to execute, and it assumes that each line in the file corresponds to a path (i.e., there is only a newline delimiter between paths.)
- Instead of using the path defined by your environment, use the paths defined in the file whose
name is the string
-h, --help
- Prints help information (for now, just the above usage statement.)
-v, --verbose
- Enables verbose mode. In verbose mode, tinysh prints out every little thing that it is doing (within reason.) This includes forks, the opening and closing of pipes, the opening and closing of file descriptors, dynamic memory allocations and deallocations thereof, and most system calls.
Once you have started the shell, the following builtin commands are available (along with the typical terminal commands):
verbose
- Enables verbose mode.
brief
- Disables verbose mode.
cd
- Changes the current working directory.
help
- Displays shell options.
pwd
- Prints the current working directory.
- Tinysh can run any typical shell command.
- It is also able to run any program in the path defined by your environment, or the path specified by an optional path file that you can provide.
- Custom (but limited) implementation of the following commands:
cd dir
- Changes the current working directory to
dir
.
- Changes the current working directory to
pwd
- Print the current working directory.
- Implements three "special features":
- Overwrite redirection:
Saves the output from the execution of
tinysh> program args > outfile
program
with argumentsargs
tooutfile
, overwritingoutfile
if it already exists. - Append redirection:
Appends the output from the execution of
tinysh> program args >> outfile
program
with argumentsargs
onto the end ofoutfile
, not overwriting any ofoutfile
. - Pipes:
Uses the output from the execution of
tinysh> program1 args1 | program2 args2
program1
, with argumentsargs1
, as input toprogram 2
, with argumentsargs2
.
- Overwrite redirection:
- Tinysh makes virtually no assumptions about the number of commands, number of paths in your path, length of pipe chains, etc.
- Contains a very detailed verbose mode that provides implementation details and control flow information (forks, opening and closing of pipes, opening and closing of file descriptors, dynamic memory allocations and deallocations thereof, other system calls, etc.) as the shell carries out the given command.
- Tinysh creates a child process for each new command, protecting the main shell process from any errant commands.
- It uses the execvp system call to execute programs.
- As a fun bonus, I implemented a tokenizer (for parsing commands) with the following features:
- Thread-safe (i.e., use of
strtok_r
.) - Does not modify the input string.
- Returns a dynamically allocated, null-terminated list of tokens and populates a provided pointer to an integer with the number of tokens found.
- Thread-safe (i.e., use of
- Add more detail to the verbose mode.
- Create static context struct for a better, more stateful verbose mode (tried to avoid this, but with the addition of any new features, it will probably be needed.)
- Add input redirection.
- Add here documents.
Ultimately, creating a full-fledged shell in such a verbose manner would be nonsensical; very few people would want that much uninteresting information cluttering up their terminal (imagine writing a decently sized shell script where every fork, dynamic memory allocation and deallocation, and opening/closing of file descriptors was explicitly remarked on!) Moreover, as you add features, the shell becomes more convoluted and loses some of it's instructional value. However, there are certainly some features that could be implemented while still maintaining the shell's pedagogical use.
So far, I think the following would be worthwhile:
- variables - both shell-defined and user-defined
- command substitution
- here documents
- basic control flow
- filename wildcarding
- condition testing
- lists