Typing “ls -l” In the Shell
What occurs within the (Bourne) shell after entering “ls -l”?
First off- what is the shell?
When using the operating systems Unix or Linux (which is considered Unix-like), the interface provided is called the shell. This is an environment that gives users access to run different things like commands, shell scripts, and programs. So the shell basically takes human readable input and translates it into a computer readable language for the kernel (where the commands are really processed).
While there are multiple kinds of shells, including C and Korn, “the shell” is often referring to the Bourne shell. This is because it’s the most commonly used shell as it was the first to appear on Unix systems. It’s also usually installed as “/bin/sh”.
Interacting with the shell
Once the shell starts up, the command prompt appears, meaning the shell is awaiting input from the user. The command prompt will vary depending on the kind of shell, but in this case a “$” would appear.
The shell will read the input from the command line once the user hits enter. The first word written is considered a command, telling the shell what it is the user wants to do. If there are any, the words following the first one are considered arguments for it.
In the example of “ls -l”, “ls” is the command and “-l” is the argument. “ls” is actually the command to print the current directory’s files and sub-directories. While the argument “-l” causes “ls” to print these out in the long listing format. This format displays a lot more information about the files and directories, such as permission details.
But first… how did the shell know “ls” and “-l” were separate when they were entered as one string?
How the user input is interpreted
The user input is actually considered standard input, which is also known as “stdin”. This is sent into the “getline” function, which uses the “read” system call in order to actually read the input. This is because “read” takes the input and sends it back into “getline” as a string, which is then sent into a parser.
The parser then uses the “strtok” function to separate the string at certain points to create an array of tokens. These tokens are strings that make up the original string. The points in which the original string is separated are referenced to as delimiters. These delimiters are made up with the whitespaces: spaces and tabs. So any time there is a whitespace inputted, they are ignored, and any strings with them in-between, are separated. This is how each word entered in the command is then recognized separately.
Where these tokens go
The parser then sends out the result of a tokenized string, an array of strings, making a 2D array. This array is then sent into a filter. This filter basically determines whether the first token, word, or command inputted, is a built-in or from an external file.
Shell built-ins are commands or functions that are executed directly in the shell. Otherwise, the command would go to some sort of external file or program that the shell would have to load and then execute.
In the case of “ls -l”, the shell would have to execute some sort of external program as “ls” is not a built-in command. But…
→ If the command were a built-in
Since the filter had discovered which command the user inserted was, the shell would send the 2D array to it’s corresponding function.
The function would then carry out the purpose of the command. Most commands would have the function print to standard out, or “stdout”, displaying the result of the execution. It is displayed within the shell terminal, right under the command prompt. This is not proceeded with a “$”, since that’s reserved for user input as it helps the user identify whether the line is a prompt or something outputted.
Some commands actually don’t seem to directly display, or output, anything to the command line at all. This is because some built-in commands do some sort of action that doesn’t print anything on success (because if there were an error than an error message would appear).
Some examples of shell built-ins would be the commands “cd”, “exit”, and “env”.
→ Else if it were from an external source
The shell would first have to check whether the inserted command was actually a standalone word or included the path to where it was.
For example: to start the shell, the user could call it by it’s path name “/bin/sh” or they could simply call it by it’s command, “sh”.
The shell may check this by looking through the characters making up the command, or the first token, and by looking for the “/” symbol.
If “/” was included within the first token, then the shell could directly go to this external location and execute the program there. Else, the shell would have to use the environmental variable “PATH”, as it’s filled with the directories the shell would have to search through for the needed executable file.
Here, the shell would have to use “strtok” again. This is because “PATH” is basically a long string with “:” delimiting each directory the shell would have to search through for the specified command. This is so the shell could add the command to the end of each directory, now considered tokens, in order to see if it exists. If it exists then it could be executed. If nothing is found then an error message would be displayed.
→→ In order to execute, the shell must fork
In order to understand what forking is, it’s important to go over processes. A process is basically an instance of a program running/executing. Everything currently being done is actually part of a process, as writing a command into a shell, or using the shell in general, is an example of a program running and executing.
So in order to actually execute the command, a call to the system call “fork” will be made. This is because “fork” creates a new child process, as the process that called “fork” is referred to as the parent. The child and parent processes are practically identical, with some differences being their purpose and IDs. These IDs are referred to as PIDs, the “p” standing for “process”, or in the parent’s case, PPID for “parent process”.
When using “fork”, the parent actually has to wait for the child to completely execute and then terminate. If the parent process doesn’t wait, then they would both run at the same time and there’ll be errors executing the commands. In order to make the parent wait, the system call “wait” must be called.
While the parent process is waiting, the child will run. This child process is what is actually going to execute the command. It’ll print anything needing to be outputted to “stdout” and/or do any of the other changes inputted by the user.
For example: the child will finally execute “ls -l” by using the corresponding executable and actually executing it. (As explained earlier in the article, the current directory’s files and sub-directories would be outputted to “stdout” and displayed in the long format showing extra information).
→→ To continue, the child must die
Since the parent was told to wait, it’s process will continue once the child terminates. This is because the sole purpose of the child was to execute the command.
After executing the command, the child had fulfilled it’s purpose and will then kill itself. Now the parent will no longer have to wait and can continue on.
Errors
If there were any errors along this process, then they would be displayed. Otherwise, the proper output is.
The errors for built-in commands and external ones are a bit different. If what the user inputted could not be found at all, an error message written to “stderr” or standard error, is printed. While bulilt-in commands also write to the standard error, the external commands use “errno” to define the error and writes using the “perror” function instead.
And the cycle continues
Once “ls -l” had been executed, the cycle starts again.
The parent process, the shell the user originally inputted text into, will go back to the beginning. It’ll print the command prompt yet again, and then wait for user input, ending with the enter being pressed.
Unless specified at some point to end, either by a certain signal, “exit”, or through some sort of error at some point, this loop will continue to occur, and the user will remain in the shell.