Abderrahmen Babchia
4 min readNov 25, 2020

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

Generally:

ls is a shell command that lists files and directories within a directory. With the -l option, ls will list out files and directories in long list format.

What really happens ?

The shell reads the command from standard input that was entered by the user. Second, after you type your command, the shell reads what you typed using the getline function. The getline function reads the entered line as one string, from the standard input and stores it in a buffer.

The getline() Function:

The latest and most trendy function for reading a string of text is getline(). It’s a new C library function, having appeared around 2010 or so.

You might not have heard of the getline() function, and a few C programmers avoid it because it uses — brace yourself — pointers! Even so, it’s a good line-input function, and something you should be familiar with, even if you don’t plan on using it.

Here’s a typical getline() statement:

getline(&buffer,&size,stdin);

The getline() function is prototyped in the stdio.h header file. Here are the three arguments:

&buffer is the address of the first character position where the input string will be stored. It’s not the base address of the buffer, but of the first character in the buffer. This pointer type (a pointer-pointer or the ** thing) causes massive confusion.

&size is the address of the variable that holds the size of the input buffer, another pointer.

stdin is the input file handle. So you could use getline() to read a line of text from a file, but when stdin is specified, standard input is read.

After reading the line and storing it in the buffer, the getline returns an int/ssize_t which is equal to:

  1. The number of characters read, on success, without including the terminating null byte of the string.

OR

2. -1, on failure to read a line (including end-of-file condition).

Parsing The User Input:

A string tokenization function is called which splits the command line into tokens. In our shell, we used a function called strtok() which took the line to tokenize and the delimiter to define token boundaries

Take for example the command:

ls -la /

We have the name of the binary (ls) and its arguments.

the command could also be:

$  ls       -la      /

We are going to write a function that will store our command (without spaces) in a char ** Which will give:

[ls][-la][/]

The shell checks if the first token (the main command itself) is an alias, and if so, replaces the alias with the actual command.

The shell will typically look in its system files for defined aliases. If the ls command is an alias for something else, the shell will replace the ls token with the string for the command that ls represents so that the correct operation takes place in the subsequent steps.

Execution:

To execute our order we will use syscall execve.

We must use syscall forkto create a new process and launch our command in it.

fork() system call is used to create child processes in a C program. fork() is used where parallel processing is required in your application. The fork() system function is defined in the headers sys/types.h and unistd.h. In a program where you use fork, you also have to use wait() system call. wait() system call is used to wait in the parent process for the child process to finish. To finish a child process, the exit() system call is used in the child process. The wait() function is defined in the header sys/wait.h and the exit() function is defined in the header stdlib.h.

On success fork returns twice: once in the parent and once in the child. After calling fork, the program can use the fork return value to tell whether executing in the parent or child.

  • If the return value is 0 the program executes in the new child process.
  • If the return value is greater than zero, the program executes in the parent process and the return value is the process ID (PID) of the created child process.
  • On failure fork returns -1.

NB : By sending a simple command like holberton to our shell, execvewe return -1 and perror display ./hsh: 3: holberton: not found.

To find where a program is, we need to use the environment variable PATH.

If we execute the command:

$> echo $PATH

We are going to have an output that looks like this:

/bin:/usr/bin:/usr/local/bin

These are the files (separated by ‘:’) or our Shell will look for our binary to execute.

We now need to write the function that will concatenate our path and the binary.

You must retrieve the content of the $ PATH variable with the function getenv. It takes a single parameter which is the variable we are looking for and returns a pointer to the content of the variable passed as a parameter.

If our binary is not in any folder, we can warn the user by one Command not found, otherwise we can execute our execve: D.

The function that retrieves the content of the $ PATH variable and returns the absolute path :D

At this point, our shell executes a command but has no builtin or environment.