By Micaela Percovich and Atenea Castillo
Understanding the shell
The word ‘shell’ has to do with the idea of something that covers, surrounds or protects something. This is specially important in the animal world, where we can find different species whose shelters and primary contact to the world are shells. In the spiritual world, a shell is perceived as a symbol of protection and hearing or light. If we transfer this simbolism to the tech world, we find that the shell is the outer layer in the system which a user interacts with, so the living body inside it is the operating system, in this case the Kernel.
Shell was created by Ken Thompson in 1971 and operates with Linux, an open source Unix-like operating system based in Kernel, which was created in 1991 by Linus Torvalds.
Shell is a command language interpreter which takes commands from the standard input (what a user writes in the command line) or from a file, and executes them generating some output (hopefully the expected one). Thus, the shell is an intermediary between the user and the computer system -kernel- (see chart below).
After a command is introduced in the command line there are two possible outcomes: in success, the result of the command will be shown; in error, this means the command is not found or it is found but doesn’t work, an error message will appear. This seems so simple but a lot of things happen in those microseconds you “wait” until you can see the output. So, let’s see the processes the shell does in that meantime:
Input: first things first, in order to start it is necessary to open the terminal or console, there, the prompt will appear, this is the first intermediary between the user and the shell and it signals that the shell is in interactive mode (see below); there the user can write commands that later will be taken as inputs. The prompt usually appears with one of these characters: $ , %, #; which are contained in the primary prompt variable PS1 that controls the appearance of the command line. After writing the command in the prompt, press enter.
- In interactive mode -this means connected to the terminal- shell connects to the terminal using [isatty] to open the shell prompt.
- In non interactive mode -not connected to the terminal-, a command will be executed but the prompt won’t appear.
Here is when the cooking starts:
- Getting the command from the prompt: as soon as you enter a command, shell gets all you typed in the prompt (from now on we will call this the string) and keep it in a buffer.
- Dividing the string: once the command is in the buffer, the shell divides (or tokenizes) it into smaller strings (tokens) separated by a delimiter, in this case a space. There are some characters that allow you to work with two or more commands in the same line (pipelines, redirection, among others), but for the sake of clarity we will assume there is only one command per line. The first token will correspond to the command and the rest to options, directories, texts, etc. Once this step is done, the shell will check if the input exists in either of these three sections: alias, built-in and PATH.
- Checking if there is an alias: the shell will check if an alias or shortname exists, if this is true it will replace the alias with the full path of the command and it will go to step 6, otherwise it will go to step 4.
- Checking for built-ins: as the first token corresponds to the command, the shell will check if this token is a part of the shell, if it finds it, this means that the token is not a program, therefore it’s a built-in created along with the shell and will be treated as such. Examples of built-ins are: env, exit, cd, setenv, unsetenv, and more.
- Checking in the PATH: If no alias is found it will look for the command into every directory of the PATH variable, in order to do this, the PATH will be tokenized using “=” as delimiter, then the value of this variable (what it is located at the right side of the “=”) will once again be tokenized using “:” in order to separate directories (see photo below).
- Making a copy of the program: when a command is found, the shell duplicates itself (this copy is called the child process or subshell) using the fork() system call. The purpose of this copy is to maintain the shell working while another program, in this case a command, is executed; additionally, it is necessary to call wait() system call while the program is running, guess why? Yes, to wait until the program is over. Once this is completed, the shell will go back to the parent process, we mean, the shell prompt.
- Executing the program: either the copy is done or not and if the command is correct (if a user enters a path, or just a command, that exists) the program will be executed in the child process using execve() system call (A system call is the way a program enters the kernel to perform some task. Programs use system calls to perform a variety of operations such as: creating processes, doing network and file IO, and much more. A list of system calls can be found by checking the man page for syscalls(2)). But, if a child does not exists a parricide will occur; the parent process will be killed in order to let the child live, so it won’t be possible to execute another program because there is no more shell.
- Getting the output: finally, after years of waiting, the user will get the output, and the shell prompt will appear again and again until she types “exit”.
What happens with the command ls -l?
After a long journey, we arrive to explain one of the reasons for this blog: what happens when you type ls -l in the shell prompt.
Ls is a command/program that lists files and directories in the current directory, so it will work in the same way as if you enter a directory in your library to check what it’s inside. When an option is added, in this case -l, this will add some information to the list (it’s called long format), i.e. files permissions, users, types of files, dates of creation.
Let’s go through the explanation given previously about the process to see how this will work. Once you enter the command, the shell will take the string you typed, then this will be divided into tokens using the space between ls and -l; after, it will look where it is located, in this case ls does not have an alias, it’s not a built-in and it will be found located inside one of the directories in the PATH environment variable: /bin/ls, after this, all PATH directories will be tokenized again with “:” and then shell will get only ls directory. At this point, as the program exists, a child will be created using fork(), ls will be executed and the parent will wait until the execution is done, the shell is now ready to give you the output you were looking for: list of files in the current directory in long format as shown in the next photo.
Is this what was expected? Yes! The shell has worked hard to help us know the contents of our directory, and is now ready to work some more, or get some deserved rest after the user types ‘exit’.