Under the hood of ls -l

Selidex Parnell
4 min readNov 22, 2020

In one of my previous posts we took a look at what happens when you enter ls *.c into a shell prompt. Since then I have had the pleasure of coding my own mini shell and now posses a better understanding of what happens when entering shell commands and so I would like to take this opportunity to go more in depth with you, this time looking at what happens when you do

$ ls -l

For the purpose of this article we will be dealing specifically with the bash shell also known sh or /bin/sh. When bash receives a command like the one above it will first attempt to decipher the command, starting by determining if any special characters such as quotations (“” or ‘’) or backslashes ( \ ), or if the command starts with any reserved characters such as elif or while. In the case of our command, ls has no special characters and is not a reserved word. So next bashwill check to see if there are any aliases, or special predefined short hands, associated with the command. This is not as straight forward in terms of examples as each terminal will have its own list of aliases stored in different locations depending on your working enviroment (in our case bash). Typically bash shells store aliases in a file called .bashrc within the home directory. If such an alias is discovered it will then replace the command with the alias. So if we had an alias for ls defined as:

alias ls='rm *'

then our command would morph from

ls -l              torm * -l

This particular alias was used in a lesson to show the importance of knowing what a command does before running it, as well as the ability to escape an alias by using quotations (meaning “ls” would not be expanded to rm * in our above example). For the remainder of this discussion we will assume that no alias has been stored for ls. Once bash has determined that the command is not an alias, it will finally recognize that it is dealing with a command at which point it will test it against 3 different types in order: shell functions, built in commands, or normal programs. Shell functions are functions defined within the shell, and are called using the format functionname( ). In our case ls is not a shell function so it moves on to checking it against any built ins, such as exit, env, history, or help just to name a few. Once again ls is not a built in so it moves on to the final step: path searching. If the command has any slashes it will run it without executing the search by running an execute system call using the command as is along with an array of the arguments that follow it. For reference we could achieve this searchless execution by entering the command:

/bin/ls -l

instead of ls -l. The end result is the same though so we will look at the second method before we discuss execution. Since we did ls without any slashes in our it will then begin to see if the file exists within $PATH. $PATH, also known as PATH, is an environmental variable that stores the list of directories that the shell is running in. For example, the $PATH of my terminal is:

/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games

This variable is separated by a series of : delimiters, so in the above example /usr/local/sbin is a separate directory from /usr/local/bin and so on. If the command bash received was not found within this path it would print out an error stating that the command was not found. For example if we tried to enter the “command” hi we would get hte following error:

/bin/sh: 1: hi: not found

where /bin/sh is the shell location, 1 is the command history index(so if we entered hi again we would get the same error but with a 2 instead of a 1), and then our error message. For our process however ls is found within the /bin/ directory.

Now that we have found our executable, either through direct path /bin/ls or by searching the $PATH, the shell will call a child process using a system call such as execve or execvp. This child process will run the executable that was found, in our case ls, passing it any arguments that were given, in our case -l.

So we are finally running our command ls -l. The ls executable lists all files within the given directory, or the current working directory if no directory is provided. The argument -l is what is known as an option and it tells ls that we want to view the files in the long format:

Once the executable is done running the child process will terminate, returning to the original shell which will display a new prompt beginning the process anew. And that is what happens when you type ls -l into the bash shell.

--

--