How the shell it works?

Natalia Vera Duran
Nerd For Tech
Published in
4 min readApr 12, 2021

Big things are made of little ones. Here I will explain in very simple words what a shell does when we type “ls -l *.c” in the command line. This can be useful to understand what is a command-line interpreter (value it) or as a resource to write your own shell in C programming.

The shell

Step 1. Each shell is a bucle starting with a prompt written on the screen like this:

$

or:

username@hostname:~$

or:

username@hostname /etc/mail>

The prompt that is going to be printed is saved in a variable called PS1, set in the file /etc/bashrc.

Some people feel creative and edit this default to show date and time, the kernel version, the full path where you are, and even it can be shown with a different color!

Step 2. The C function getline obtains a line from the input wrote in the command line and saves it in an array of characters, that we call full string here.

char *fullstring[] = {ls -l *.c};

Step 3. The string is analyzed to tell if the array is empty to do nothing and shows the prompt again, or if the array is an EOF. When you type Ctrl +D in the command line it's interpreted as EOF, which means the End Of File. It’s a macro declared in “stdio.h” which means -1, and if an EOF is found, the shell must exit.

Step 4. A loop walks through the full string, looking for expansions *. When it finds an expansion it walks through the next characters until it found an space or a new line “.c” and then compare one by one, the names of the files in the working directory, saving their names in an array named expansion like this:

char *expansion = {file0.c file1.c file2.c}

Step 5. The ‘*.c’ is replaced in the original array by the names of all the files ending in .c in the current directory, changing the original size of the array.

char *fullstring[] = {ls -l file0.c file1.c file2.c};

Step 6. The string is analyzed by the C function strtok. It break the string in one new part each time that a space is found in the string.

Step 7. All the tokens are saved in an array of pointers like this:

char *arrayofparts[] = {
"ls", ->token0
"-l", ->token1
"file0.c", ->token2
"file1.c", ->token3
"file2.c", ->token4
NULL ->token5
};

Step 8. When we have a command that we use too frequently (like gcc with all the flags to compile beautiful programs), you can declare an alias to avoid writing so many times the same long string. That is made for a C function called alias and the shell must check if the command “ls”, is an alias or not. If an alias is found, it's replaced by all its content.

Step 9. The shell has built-ins functions that don’t depend on the programs that you already have on your computer or virtual machine. So, “ls” is compared with the built-ins included. If a built-in is found, it’s executed directly.

Step 10. The executable files in a computer or virtual machine can be found in very different directories. Your machine has an environment variable called PATH. It list all those places. So, “ls” needs to be searched in all the directories contained in the variable PATH. You can check those paths in your machine writing:

$ echo $PATH

or

$ env

Assuming that the shell is written in C, it can process the global variables using the declaration of the macro like this:

extern char **environ;

Or it can use the third main prototype:

int main(int ac, char **av, char **env);

In any case, all the environment is split into lines, to compare if the beginning of the line is the same as “PATH=”. ls is found in the directory bin, so, it is changed to /bin/ls.

Step 11. If the program is found in any of those directories, the shell creates a child function with the fork system call and waits until the child is executed completely, with the system call wait. The child uses execve to execute /bin/ls. Execve requires the environment variables, too.

Then ls prints the long description of all the files ended in .c inside the current directory and returns zero.

$ ls
5_helper2.c builtins_list.c helpers_print.c README.md str_tok.c
a.out execute.c helpers_string.c shell.c tokenize.c
builtins.c find_in_path.c man_1_simple_shell.1 shell.h

Step 12. The return value of the command ls is printed in the standard error. If ls ended successfully, it shows a zero in the standard error. The standard can be shown if you type:

$ echo $?

Step 13. The prompt is showed again.

$ 

Step 14. Getline waits for new instructions from the command line.

My team

Logo from Holberton School
Holberton School

Our own version of the shell was created by my team from Holbeton School:

  • Jerson Perez, Full Stack developer
  • Edher Ramirez, Full Stack developer
  • Natalia Vera, Full Stack developer

It can be found in our git repository. Let us know your questions and opinions!

About me

I am a passionate software developer from Holberton School and a Psychologist from the National University. During all my life I have been developing valuable professional skills as being a good listener, critical thinker, and team player. I have been recognized as a very intelligent and empathic person. Whether on work or academic life I want to create meaningful experiences and inspire my partners. I am consistently dedicated, and curious.

If you want to create a connection with me, follow me on GitHub or Twitter.

I hope you enjoyed this reading, and thank you for your attention.

Made by Natalia Vera Duran.

--

--