Ana-Morales
5 min readAug 14, 2020

--

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

Have you ever wondered what happens when you type “ls -l” in the shell? We already know “ls” is a Linux shell command that lists directory contents of files and directories and with the “-l” option, “ls” will list them in long format. But how does it do it? What happens behind the scenes? Let’s review it a little more in detail.

Basic concepts

The Shell: The shell is a program that takes commands from the keyboard and gives them to the the operating system to perform. On most Linux systems a program called bash acts as the shell program. Through the Shell the user communicates with the kernel, writing commands on the command line

The Kernel: The kernel actually does a lot of things. One way to sum it up is that it makes hardware do what programs want it to do. The kernel also manages the file system, inter-process communication, and many networking things.

Systems calls: A system call is a controlled entry point to the kernel, allowing a process to request that the kernel perform some action on behalf of the process. The kernel makes a range of services accessible to programs through the system call application programming interface (API). These services include, for example, creating a new process, performing I / O, and creating a pipe for inter-process communication.

What happens when you type ls -l and hit enter?

1. Get the input.

First of all, the Shell prints the prompt (command line) where the user must type the command to be executed, in this case “ls -l”. The prompt is printed on the screen when the execution of each command entered is completed. This shows that the prompt is an infinite loop that continues until the user exits the shell completely.

To obtain the command from standard input, the getline () function is used, with which the text entered by the user is read and stored in a buffer for later manipulation. The getline() function is prototyped in the stdio.h header file. See man 3 getline for mor information.

2. Parse user input

Since we have the string with the input, it must be tokenized. A token is a word that has been separated from a string based on a defined delimiter. For this the strtok () function is used.

So, for example, if the command written by the user is:

using a space as a delimiter, the next tokens will be obtained:

3. Check for alias and built-ins

The shell will start to check if the first token matches any aliases or builtin commands.

An alias is a user-defined command and not built into the shell by default. If the first token is an alias, the alias replaces the token with its value. If “ls” is not an alias, the shell checks if the word of a command is a built-in.

Built-in commands are those that have their definition in the shell’s own code. These has the ability to affect the state of the shell. If the command is not a builtin, the Shell will look for it in the PATH.

4. Check PATH

On Linux, the “ls” command is a file that contains the executable program to run the “ls” command. So when you type a command, the shell will try to locate the command (file) by searching your disk and directories on its PATH which is a list of directories where the shell searches every time a command is entered.

The PATH looks something like this:

The directories are separated by semicolons and each one contains many other executable files. The command “ls” will be found in the directory “bin” so this search returns the path /bin/ls as the command that will be execute.

5. Execute the program

To executes a new program, ls in our example, the shell has to make some system calls: fork() -execve()-wait().

With fork() the shell creates a new process by duplicating the calling process. The new process is referred to as the child process. The calling process is referred to as the parent process.

A new process must be created because in the successful execution of a program, like “ls”, it is necessary for that process to end and not return to the parent, what you want is to run the child while the parent waits and once the child finishes running , the parent continues its execution to continue making new calls to other commands.

When the new process is created is possible to give the child process instructions to complete and, in the parent process, the syscall wait() is used to wait for state changes in the child.

While the parent waits, the child uses the execve () syscall whose prototype is:

int execve(const char *pathname, char *const argv[], char *const envp[])

This syscall executes the program referred to by pathname. This causes the program that is currently being run by the calling process to be replaced with a new program, in this case the new program will be /bin/ls, so the first argument passed to execve will be /bin/ls which was obtain in the previous stage of the process (stage 4. Check the PATH).

The input to execve will be something like:

execve(“/bin/ls”, {“ls” or “/bin/ls”, “-l”, NULL}, NULL);

In this stage is where the command written by the user will actually be executed. Once it is done and the child process has ended, the parents process continues with it’s instructions printing the prompt again for the user to type a new command.

More info:

M. Kerrisk. The Linux Programming Interface

man 3 getline

man 3 strtok

man 2 fork

man 2 execve

man 2 wait

--

--