When we execute the command “ls *.c” in the shell, we expect to obtain as a result a list of all files ending with the extension “.c” within the queried directory.
For this, we have used the command “ls”, a wildcard “*” and the search argument “.c”
But … What is really happening?
The blinking cursor is indicative that the Shell is waiting for us to write a character, that the stdin will read and buffer it, then write the character to the console, so that we see what we are typing, and so on until we have our command line “ls *.c”.
Once we press enter …
The keyboard driver recognizes that characters have been typed and passes them to the Shell. The getline function reads the entered command line as a string from standard input and stores it in a buffer.
Then the process called tokenization begins, a string tokenization function is called that splits the command line into tokens, thus removing the blanks. Now our command has two tokens, “ls” and “* .c”, once finished, the information is stored in an array of strings.
With the array tokenized, the shell checks whether the first token (the main command itself) is an alias, and if so, replaces the alias with the actual command. Normally, the Shell will search your system files for defined aliases. If the “ls” command is an alias for something else, the shell will replace the ls token with the command string that “ls” represents for the correct operation to take place in the following steps. If it is an alias, it is saved as a token after removing the spaces as before and it is found again the aliases are verified.
The next step is to check if each token is a built-in function or not. If the command is integrated, the shell executes the command directly, without invoking another program, if it cannot find it, it will need to look for it to execute it. For example, “cd”, “pwd”, they are built-in commands, “ls” is not built-in, so now the system needs to find the executable for “ls”.
After built-in verification, the shell will clone itself or create a new process (child process) to run at the same time with the first process (parent process) using the fork () function. This is done so that the shell can return to the prompt after it completes or fails.
If the return value of the fork is the ID of the child process to the parent, it returns 0 to the child process or -1 if it failed.
The next step is to verify the PATH. All the process in this step happens within the child process and the parent process will wait until the child process finishes. The shell will take the PATH environment variable and check if the command “ls” is in the directory list of the PATH variable.
The Shell will take a copy of the PATH value and tokenize it with the delimiter “:”, the result after tokenization is an array of strings, each string is a path to a directory.
The shell will then concatenate the first string “ls” after tokenizing the buffer in the previous step and check if the path exists (for example, /usr/bin/ls). If it does not exist, the shell will move to the following directories. If there is no path, it will return to the main process.
If the path exists, it will execute the command and its option argument.
After the child process completes, the parent process will run and return to the prompt using PS1 (request string 1, also known as a built-in shell variable, allows users to log in).
After printing the prompt, the Shell is waiting for you to enter the new command.