What happens when you type `ls -l *.c` in the shell?
In this document, we are going to describe the process done by a shell to process the following order:
ls -l *.c
Before we start, it is important to define the meaning of the word shell. the shell is a program that takes commands from the keyboard and gives them to the operating system to perform. The command describe below will be entered into the terminal (which is a program that opens a window and lets us interact with the shell) and will be process by the shell.
The below command will show us in the terminal the list of files in the current directory using a long listing format. In this case, only files with the extension .c will be display.
ls: — list directory contents.
-l: Long listing format
*.c: only files with the extension .c will be display.
To read this command, we divide this process in three sections:
· Read
· Split
· Execute
Read
During this section, we take the commands and options entered and pass it thought a filter to make sure that it is not an EOF. EOF means the end of the line and occurs when the user press control + D. This creates a value of -1, which the shell will read as exit the shell. In case the command is EOF, we use the getline () command to read the line, if we confirm that the value is -1, the shell clears the memory and exit the shell.
Split
Once we have obtained the command, we create a routine to segment that command and arguments in several parts. These parts will be in a pointer array. For this purpose, we use the strtok command. This command breaks a string into a series of tokens using the delimiter.
Here is an example:
In our example, the strtok with use empty spaces as delimiters and store the tokens into an array of pointers.
In our example, the strtok with use empty spaces as delimiters and store the tokens into an array of pointers. Here is an example from a string.
Execute
After the tokenization, we start with a selection section. Also, the first position of this segment will be passed through a function selector. In this place will be determinate if the command is built-in or not built-in. So, we have two ways.
The built-in command could only change the shell’s operation if they were implemented within the shell process itself. For example, if there was a program named exit, it wouldn’t be able to exit the shell that called it. That command also needs to be built into the shell. So, it makes sense that we need to add some commands to the shell itself.
There are the no built-in commands. These commands do not need to build into the shell. Because of this we need to pass it though a special condition. In this case, we are going to look at the environment to find the PATH.
To check the different environments in the shell, we must type env and press enter.
As you can see in the following picture, you can see the PATH environment.
Once we found the path, we must tokenize all the folders that are located inside the PATH environment. In this case we are going to use the : symbol as a separator. For this task, we are going to use again the strtok function. The divided folders are going to be saved in an array made of pointers.
At this point we have two arrays of pointers, one made of commands and the other made of folders inside the PATH. From this part on, this section is critical. The shell at this point takes the array of pointer corresponding to the folders and merge it to the command inserted by user. It is very important to know that a slash will be added between this two. To do these multiple merges, we use the strncat function. You can see an example below:
/usr/bin / ls
Once the merges are complete, we must check the environment and find a match between these merge string and the folders inside the PATH. For this part we use the stat function. At this point, the stat will tell us if the command “ls” exists or not. In this case, the function we are looking for (“ls”) is found we must execute the program. Here is an example of an implementation of the stat function.
Not so fast. Before the execution, we must use a command call fork. This command will make a copy of the program we are running. After this part, the concept of father and children is introduced. These two are created using the command fork. When the children will have an PID (program ID) equals to zero, the father will have a different PID. In this part, we must check this PID to let the children go first.
To clarify the concept of fork, in the example below, you can see the fork command used three times. Every time the fork is used, every process creates a new child.
When the children will have an PID (program ID) equals to zero, the father will have a different PID. In this part, we must check this PID to let the children go first. This part is important because once we execute the program with the execute command, the program will be terminated. Therefore, we must use the child program and leave the father alone. For this part, we have used the wait command. By doing this, the father will wait until the child program is run and terminated. After all this, the results will be shown on the prompt and the shell will be waiting for the next instructions. In the pictures below, you can see an example of this implementation.
Flowchart
Below you can find a flowchart of the process done by shell.
Sources:
(Don´t go out without checking these amazing sources)
………………………………………………………………………
Authors:
Juan Pablo Garcia Osorio < 3387@holbertonschool.com >
Juan Camilo Gonzalez Bautista <jcgonzalezb@gmail.com>