Writing a Shell in C
Do you have dreams about writing C shells by the sea shore? That makes two of us.
One of the many cool projects you can make while learning to program in the Linux environment is to make a command-line interpreter like the Bash shell or the command prompt in windows. In the process you will learn how to handle fork + exec calls and the various system calls associated with the working of a shell.
We will first implement a very basic shell and then improve upon it by adding various features like redirection,pipeline,globbing etc.
Prerequisites:
-
Basic Knowledge of the Linux operating System, the Bash Shell and C programming Language .
-
A Linux machine or the Windows Bash Subsystem with the required packages.
-
The gcc and the readline dev packages packages
Install the packages by typing
sudo apt-get install gcc libreadline*
Lets begin by writing a simple version of the whole shell
|
|
Note: We are not executing the command or even checking if the entered command is valid just taking a command showing the prompt. we will add those features later.
If you are familiar with C the whole program looks easy enough, we take a command from the user in a prompt if the command is ’exit’ we close the shell.
The only thing different is the way we take input, it is done through readline.
Readline if you look at the man pages(‘man readline’) it says
“Readline will read a line from the terminal and return it, using prompt as a prompt. If prompt is NULL or the empty string, no prompt is issued. The line returned is allocated with malloc; the caller must free it when finished. The line returned has the final newline removed, so only the text of the line remains. readline offers editing capabilities while the user is entering the line. By default, the line editing commands are similar to those of emacs. A vi-style line editing interface is also available.”
We use readline instead of using the standard input library functions because of the line editing capabilities of readline and the auto-complete feature that comes with the prompt (also bash uses readline sooooooo).
Lets save and run the program
- Save the file as ‘myshell.c’
- open up a terminal and compile the file using the command
- ‘gcc myshell.c -L/usr/local/lib -I/usr/local/include -lreadline -o myshell’
- execute by typing ‘./myshell’
If you are thinking why the ’ls’ command is not working its because we have not written the related code yet. Our basic shell now just takes a command checks if it is ’exit’ if so it terminates otherwise prints the ‘>’ prompt again.
You can also see the auto-complete with readline working on the filename in the same directory. if you press [tab] key after a my it autocompletes with all the filenames starting with my.
Lets see how our shell will work in its entirety using a flow chart diagram
The Parser:
The parser takes the input from the prompt and separates it into the command and arguments.
The various we followed for writing the parser are
- The command and the various arguments are separated by spaces.
- If an argument is inside " " it is treated as a single argument even if separated by spaces.
- Eg a directory name may contain spaces in its name ’ls “Program Files”’ it should not be treated as ’ls Program’ and ’ls Files’.
|
|
we make the necessary to our ‘myshell.c’ program
|
|
Lets compile the file and test the functionality of the parser to see if it works.
The parser works as expected the different tokens are shown inside the [ ] along with the no of tokens. You can turn of the debug function by setting debug=0
and recompiling.
Whats next?
We have taken the input from the prompt separated it into commands and arguments, now we call the write the execute module that handles the various fork and exec calls for the external programs and to check if the command is a shell built-in(like cd).
Lets implement the execute module, which takes the command and the arguments and forks a child process and runs it either in the background or waits for the process to end.
We use to ‘&’ trailing character if we do not want to wait for the command to complete.
Lets look at the code
|
|
we save the above code in file called called execute.c
and make the following changes to our parse.c
file.
|
|
Save all the files and recompile.
It works. :)
Note: Every command you execute in the command line in Linux is either a shell built-in or an external binary forked using fork+exec. Our shell will run external binaries but will not run any shell built-ins like cd
(change directory) etc. The shell built-ins as the name suggests are a part of the shell and have to be implemented inside the shell.
Additional Features
- Shell Built-ins
- Globbing
- Redirection / Pipes
- Signal Handling
- Color Schemes and Prompt.
See the entire project here: Z-Shell