What Actually happens when you type `ls -l *.c` in the shell? (How a Basic Linux Shell works)

Emmanuel Udeji
5 min readNov 3, 2021

Normally the shell command ls -l *.c is like a short-hand for listing all the files with .c extension in a directory in a long format. However, what you may not know is how the Linux shell interprets this command and prints the output of a long list of files contained in the specified directory. In this article, I will show you how a basic Linux shell works with the example of what happens when you type ls -l *.c on your terminal. My team worked on this project (Building a Simple Shell) during our DevOps training at Alx-Holberton School.

First, let’s see the result of entering ls -l *.c in the shell terminal (command line).

Image showing the ls -l *.c of the current working directory

Generally, the command ls will list the non-hidden files contained in a directory. With the addition of option -l, it will print these files in a long format showing other attributes of the files like its file permissions. While the ls -l *.c will only list the files with .c extensions but this time in a long format. With this understanding let’s see how the Linux Shell interprets commands.

Let’s see the Basic Concept of a shell

The shell is a computer program that provides a user interface to access the services of the operating system. This could be the GUI in Windows Desktop or the Command Line Interface in the Power Shell. In Linux, the Shell will establish communication between the kernel and the user of the Linux System. In this case, we are discussing the shell as a command interpreter in Unix systems.

The shell does these 3 basic things.

· Read: Reads the command from standard input.

· Parse: Separates the command string into a program and arguments.

· Execute: Runs the parsed command.

Here, let’s translate those ideas into code in the form of the shell_loop() function.

A simple shell loop

To understand how the shell will interpret the ls -l*.c command, let’s walk through the code. The loop receives the argv (command-line arguments). Then make declarations. The do-while loop is used for checking the status variable because it executes once before checking its value. Within the loop, we print a prompt $, call a function to read a line, call a function to split the line into args, and execute the args. Finally, we free the line and arguments that we created earlier. We’re using a status variable returned by execute( ) to determine when to exit. This is a simple concept, but note that in terms of architecture the shell is more than just a loop.

To see the complete version of the code for the simple_shell project check our Github Ripo.

Here is what the shell does to interpret ls -l*.c

1. It reads the argument from the command line: When the command is entered, the shell reads the argument and if its length is not NULL, it allocates memory for its storage. It is also stored as part of the log’s history in case you want to see what command was entered at a certain time. An example of a NULL command would be when you just click enter on the terminal. If this happens, then the signal will exit and prompt for the next input.

2. The Shell Parses the arguments: Parsing means to split a sentence into its constituent parts and describe its syntactic roles. In computing, parse is used in strings, texts, and commands. At this stage, the computer will split the command into an array of “ls” “-l” “*.c”. This is referred to as tokenization.

3. Checking for special characters: At this stage, the shell checks for special characters like pipes |. The pipe character enables the redirection of the output of one process into another process instead of the terminal window (STDOUT) for further processing. Thus, by piping, the output of one process becomes the input of another process. In this case, there’s no pipe in ls -l*c.

4. Checks for built-in commands: The shell has Built-in commands that are stored in a library. This can be created locally through shell scripting to reduce the repetition of certain tasks while coding. A command like ls is a built-in function. When executed it is expanded to list files in the directory. The -l is an option for the arguments of a command. It is expanded to mean a long format. The *.c is expanded to mean all files in the current directory that end with a .c extension.

5. Executing system commands & libraries: The shell is made up of System commands and libraries that are executed using system calls. Through System calls, programs do interact with the Operating System. A computer program makes a system call when it makes a request to the operating system’s kernel. The tokenized arguments are executed through the system call — fork(). The fork() system call is used to create a new process called a child process that runs concurrently with the process that made it (parent process). Both processes will execute the next instruction following the fork() system call. One can use the execvp() call to differentiate the processes run by the parent and child processes.

6. Printing current directory name and prompting for next input

Finally, once the shell executes the program, the last step is to display the current directory name and a prompt for the next command, (usually “$ ”).

Conclusion

Now, you know much better how the shell interprets commands like ls -l*.c and interfaces between the standard input and the system kernel and gives standard output or error message in case of invalid command input. The implementation is really interesting, but way more than we could fit into this article. However, understanding the basis makes it possible to relate to the program better and develop better projects with it. We invite you to try this project so that you can get to know and learn this language more easily!

Thank you for stopping by to read. Let us know in the comment what you think about the work. We also welcome your contributions for future updates.

Authored by:

Emma Udeji

Pericles Adjovi

--

--