What happens when you type ls -l in the shell?

Silena Restrepo
7 min readAug 15, 2020

--

Since we are going to review the process from the very beginning, it’s really important that we see some concepts before getting into the real matter.

What is “the Shell”?

It is a program that takes commands from the keyboard and gives them to the operating system to perform. On most Linux systems a program called bash (which stands for Bourne Again Shell, written by Steve Bourne) acts as the shell program, but of course, this is only one of the several options we have.

What’s a “Terminal?”

Or terminal emulator, it’s a program that opens a window and enables a machine to connect to and communicate with another machine using a command line or graphical interface (the Shell). There are a bunch of different terminal emulators you can use.

What are “commands”?

In computing, a command is a directive to a computer program sent by the user to perform a specific task. In simple words, they tell the computing device what to do or execute next, depending on the command sent.

This is a chart with some of the most common ones:

Many programs allow specially formatted arguments, known as flags or options, which modify the default behavior of the program, while further arguments may provide objects, such as files, to act on. Comparing to a natural language: the flags are adverbs, while the other arguments are objects.

Now that we know the basics, we can continue to review our main question:

What actually happens when we type ls -l in our shell?

First of all, ls is a command that lists information given to it via standard input about the files (on the current directory by default) and writes the results in alphabetical order to standard output. It can take several arguments such as
-a ( do not ignore entries starting with ‘.’), -r (reverse order while sorting),
-s (
print the allocated size of each file, in blocks), and so on… but our key flag today is typing ls -l, so here is an example of what we get when we do:

What we know up to this point is that with “ls” we can list information, but when we add “-l”, we can see all the files and directories in the current working directory but displayed in a long format, headed with the respective permissions, the owners and users, and dates and time in which they were created.

But what does it really happen behind the scenes?

We have 4 main steps our shell performs to do this whole process with the commands:

  1. Display prompt
  2. Read
  3. Interpret
  4. Execute

But there are more “smaller” processes in between these 4 steps.
Let’s go ahead and review each one of them:

  1. Display prompt: “$” is printed to initialize a new line, and what is typed after it, is what is going to be read by getline().

2. Read command with getline():

This function reads an entire line from stream, storing the address of the buffer containing the text into *lineptr. The buffer has a terminating null byte and includes the newline character if one was found. If *lineptr is NULL, then getline() will allocate a buffer for storing the line, which should be freed by the user program.
Alternatively, before calling getline(), *lineptr can contain a pointer to a malloc allocated buffer *n bytes in size. If the buffer is not large enough to hold the line, getline() resizes it with realloc, updating *lineptr and *n as necessary.
This is how our function’s prototype looks like:

ssize_t getline(char **lineptr, size_t *n, FILE *stream);Basically what it does, is that it waits for the user to type in any character, reads and stores it into a buffer and finally waits until you press enter or (Ctrl+D).

3. Interpret and parse a command with strtok():

This function breaks a string into a sequence of individual tokens. On the first call to strtok(), the string to be parsed (cut) should be specified in str. In each subsequent call that should parse the same string, str must be NULL. Each call to strtok() returns a pointer to a null-terminated string containing the next token but not the delimiting byte. If no more tokens are found, strtok() returns NULL.

Validation process

On this step, we got 3 conditionals we need to verify if true, that are (1) if there are any aliases or special characters, and(2) if it’s a builtin command.
- Aliases: refers to the situation where the same memory location can be accessed using different names. So after the two previous steps are done, the Shell will check for any alias that matches what the user meant in the command type and what it has internally stored. If any alias is found, it will expand that alias before executing the command.
-Special characters: after the previous process gets done, it will then search for special characters like: “,‘, \, *, &, :, #” and execute the logic associated with the special character.
-Builtin commands: finally it checks if the buffer has entered built-in commands( is a command or function that is part of the shell itself).
Some of them are break, cd, echo, exit, help, pwd, read, and so on…

PATH process

PATH is an environment variable in Linux and other Unix-like operating systems that tells the shell which specific directories to search for executable files in response to commands issued by the user. What the Shell does with this is that it searches for ls in the PATH variable to make sure it’s not a builtin command, so it then copies the environment and passes it to the new process to modify the path. Although this variable is a list of directories that the shell searches for every time a command is entered, once identified the path or its content, they will be parsed (cut) into smaller parts every time the delimiter “:” used to separate them is found.

Fork() and execve()

The first function fork() creates a new process by duplicating the calling one. The new process is referred to as the child process and the calling process is referred to as the parent process using this form: pid_t fork(void);

The second function execve() executes the program pointed to by filename, which much be either a binary executable or a script starting with a line of the form: #! interpreter [optional-arg]

If the file exists: forking and execution are done in the program of the child process. When executing it, three system calls (which provide an essential interface between a process and the operating system) will be seen by fork”, a system call that creates a clone of the current process copying data such as the environment among others, and passing it to the new process, execve” that executes the program that is referred to by the pathname and wait” that suspends the execution of the current process until one of its children finishes.

So, once the fork requirements are met, exceve is called to stop the duplication of the parent process, runs the command and loads ls, (or the new process in this case), replacing parts of the current process. Then wait is called so the parent process waits for the child to finish its regular course while the parent process continues to do other things and keeping track of the children at the same time. Now when it finally reaches the Shell system again, to exit depending on the result or status in which the parent is at. And finally, after a child process terminates, the parent process will print the prompt to the user.

4. The command typed in the first place is executed and if error, it will print the message specified according to the situation:

  • If success, (ls -l), the correct execution of the command is shown.
  • If we type something the Shell wouldn’t recognize like (hola), it would
    print “hola: not found”.
  • If we misspell something like (ls l), it will display “ls: cannot access l: No such file or directory”.

And once this is done, it will print the prompt again to the user again.

Made by:

  • Francisco Guzmán
  • Silena Restrepo
  • Lilibeth Tabares

--

--