How Does the Shell Command ls Work?

Let’s fully break down and understand a command input for the Shell, one step at a time— ls.

First and foremost — what exactly is the Shell? Besides as the protective outer layer of turtles, I think of the Shell as how we interact with our computers. The Shell is a command line user interface that feeds keyboard input to a given computer’s operating system. The most common and popularly-used Shell program is bash, which is utilized on Linux systems. Other programs for differing operating systems exist, but for the purposes of this article, I will be working in bash.

We utilize the Shell in terminal emulators, programs that open windows permitting interaction with the Shell (and, in turn, interaction with our computer). It is in a terminal emulator that a user is given access to the command line. At the command line, users enter keyboard input after a displayed prompt string. This displayed string is stored in the PS1 environment variable, one among many environment variables available by default in all Shells. The default prompt string stored in the PS1 variable is “\s-\v\$” although the actual string displayed is dependent on your personal machine and login.

So, we are showered, dressed, caffeinated and at the command line ready to interact with the Shell. Go ahead and type the command ls after the prompt:

Stop! Were you just about to order the Shell to do something for you without understanding the underlying system at work? Let’s slow down.

When we type anything at all at the command line, ls or otherwise, the Shell undergoes a three-step search process. First, the Shell tries to match the keyboard input with any aliases defined in the system. Upon successful matching, corresponding text is replaced before the Shell proceeds to search for the entire command. For example, if our system included an alias that defined ls as cd, the Shell would replace our input with the text cd before proceeding (to learn more about using this cool Shell feature, read this). Otherwise, and as is the case with our ls input here, the Shell begins to search for the entered command as given.

Second, after attempting to match aliases, the Shell checks if the first word of the entered command represents a built-in command. Built-in commands are contained directly within the Shell, and thus can be executed automatically, without the need to run a separate program. Different Shells feature different built-in commands — one such commonly-used one in bash is echo. You can view more at this link.

Finally, after replacing any corresponding aliases and in the case of failing to identify a built-in command, the Shell proceeds to match the input with a process identifier. Linux works on two classes of content — files and processes. The ls command represents the ladder, an executable program identified by a unique process identifier (aka. PID). When the Shell searches for a given command, it searches for its corresponding PID in another environment variable, PATH, which contains a colon-separated list of directories. The Shell searches one-by-one through this list until it finds the identifier in question. Let’s “echo” (pun intended) our aforementioned built-in command to view the list of directories stored in our example PATH variable:

So, in this example, the Shell searches first in the /usr/local/sbin directory, then in the /usr/local/bin directory and so on until it locates the ls PID. Here, it so happens that our desired executable is located in the /bin directory:

Alright, so we’ve located the executable PID that will allow us to run the ls command. Now, let’s run it! This time, go ahead and hit <Enter> to execute our command:

Beautiful!

…but what exactly happened?

When executed, the ls process identifier instructs the Shell to list all contents of a specified location (using the format ls [directory]). Here, in executing ls by itself without specifying a location, the Shell resorts to listing all contents of the current working directory. In our example, I happen to be in the /home directory, the directory in which users typically keep their work (you can view the name of your current working directory by entering the command pwd). In this case, my home directory contains the directories holbertonschool-low_level_programming, holbertonschool-zero_day, holberton-system_engineering-devops and my_first_repository as well as the files ether.c, philz.c and way.c.

After executing the command, note that the bash Shell immediately returns to the command prompt.

The ls command is quite useful by itself; however, it additionally features a multitude of options that permit specification of certain files and descriptions. For example, say we wanted to view only those files in the current working directory that ended in .c. How would we do this?

Again, baby steps. Back at the command prompt, add the single character * to our ls command. I’ll let you go ahead and run the command immediately this time:

Even more beautiful!

…but what happened?

With *, we’ve included what is called a wildcard character. Wildcards are special characters that permit what is called pathname expansion. In other words, upon encountering a wildcard, before running the command, the Shell expands the character to represent select filenames. In fact, wildcards were implemented specifically for the sake of permitting rapid specification of groups of files. You can view a complete list of such special characters at this link.

The * wildcard is a catch-all that matches any pattern of characters. By executing ls *, we are executing a pathname expansion that commands the Shell to list everything, including all contents of all directories in the current working directory (ie. file or directory names with character patterns of all lengths, sizes, order, colors, allergies, music preferences, etc.). In our example, we now additionally see the contents of the four directories contained in my current working /home directory.

Now, we can further specify our pattern to only match the files we want. Type ls *.c after the command prompt and again hit <Enter> to execute.

Why is this list smaller than the results in the previous commands? In entering ls *.c, we are commanding the Shell to execute ls on a more specific selection criterion. Here, we are commanding the Shell to list all files in the current working directory with names that match any set of beginning characters, but only those that end with .c. In my case, we now see only those files ending in .c — ether.c, philz.c and way.c.

With an added mint leaf, please!