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

Hachem Hfaiedh
5 min readApr 16, 2020

--

Here we are writing about this “again” but this time with a deeper look and understanding that we acquired trying to create our “own shell” thus let’s go over it all again , sharply and definitively.

To start off , let’s remember what’s a shell and all its “jargon”:

  • A shell is a program that takes commands from the keyboard and gives them to the operating system to perform exactly the kernel “ it is a vital component as it is the basis for the proper functioning of the entire system. Kernel performs a variety of tasks including process management, managing hardware devices and handling interrupts”
  • The terminal also known as command lines or consoles allows us to accomplish and automate tasks on a computer without the use of a graphical user interface.

Now it is time to open the terminal and type the command.

Once you’re there a line appears at the top of the terminal, before even entering a character, this is the prompt ending with the ‘$’ sign.

ls will list all the files in the current working directory.

  • -l, its argument uses a long listing format that streams the permissions for the owner and the group, the last modification time and the size of a file.

All of the previous occurs just the moment you hit ENTER though the track of this process is pretty impressive.

In the beginning the shell program checks for any aliases associated with the commands entered. An alias is a string that we assign to something else. After that, the shell looks through the first word of a command to check if it’s a built-in function before checking for a program in the PATH.

Then the shell uses either its “getline” a function in the library <stdio.h> or its system calls to read from the standard input.

The getline reads an entire line from stream, storing the address of the buffer containing the text into *lineptr. The buffer is
null-terminated and includes the newline character, if one was found.

ssize_t getline(char **lineptr, size_t *n, FILE *stream)

After conserving the input in the buffer , the upcoming step is to search for the command and execute it. To do so, we parse the string using strtok and the delimiters “\n\t\a\r\v” which returns the tokens stored in an array.

In our example the delimiter is the space so if we type ls -l /dir the return will be:

array[] = {ls, -l, /dir, NULL}

So the first element represents the command and the rest are its arguments but ls is an executable file.

The executable files are usually stored in some specific directories such as:

/bin → user utilities — contains some common executables used to navigate directories and manage files via the command line (cd, ls, rm, etc)

/sbin → system programs and administration utilities — contains executables that handle things like booting, restoring, recovering, and/or repairing the system (reboot, mount, etc)

and various others who are stored in the PATH variable accessible via terminal when typing simply its name, its value will be this:

bash: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:

each directory is separated with a “:”.

(ps: all the environment variables are stored in “env” in the form variable = value)

The shell parses the directories in PATH, one after another, in order to find which directory is the container of the given command and to verify its validity it will use the stat function.

int stat(const char *pathname, struct stat *statbuf);

EXECUTE EXECUTE EXECUTE !!

Whenever we’re on the right path the shell program forks the process, and saves the return value. Once fork() returns, we actually have two processes running concurrently a parent and a child process . The child process will take the first if when pid == 0.

Then we use one of the execve from the <unistd.h> and it is one of multiple exec functions delivered.

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

execve replaces the calling process image with a new process image. This has the effect of running a new program with the process ID of the calling process. Note that a new process is not started; the new process image simply overlays the original process image. The execve function is most commonly used to overlay a process image that has been created by a call to the fork function.

If we apply execve to our example the filename is the path founded /bin/ls and argv[] is our array of tokens while the envp[] for each character string pointed to by the array is used to pass an environment variable to the new process image.

In the meanwhile the calling process or the parent process is blocked until its child processes exits .

After all this work the shell should clean after himself w leave no trace waiting for a new command , and what it does is that it frees all the allocated memories through the processes.

Now if that’s all for you today and you want to exit the shell there are two main options either your type exit in your terminal or press ctrl and c keys on your keyboard which is a predefined signal to wrap things up.

written by:

Imen Ayari

Hachem Hfaiedh

--

--