What happens when you type ‘ls -l’?
So you’ve hacked your way around a terminal, neat. You’re probably familiar with basic commands like mkdir, cd, rm, and ls, but have you ever wondered what exactly makes them work the way they do? If the answer is yes you’ve come to the right blog! As you may or may not know your terminal comes equipped with a shell which is a fancy way of saying it’s capable of performing all sorts of neat tricks and, like any good magician, it prefers to keep the intricacies of those processes hidden away.
It does so by interpreting input, in this case via text you type into the command line, and converting that input into the result of the desired process being called. In other words it takes whatever you type (the string “ls -l” for example) as input, reads it, understands “oh, you’re trying to get me to list something, and in this particular way!”. Then it goes and finds everything it needs to perform the action and it does just that: it lists the contents of the current directory in long format. Well that’s all well and good but we didn’t really explain anything other than saying “the computer is smart, it understands what we want and does it for us!” and that is the opposite of what we are setting out to prove. Computers are dumb. Very dumb. To illustrate just how dumb computers are, let’s run through the actual low-level (tech-speak for the way computers think and communicate) processes involved in the execution of the command ‘ls -l’.
The first step is interpretation: the computer receives your input (the keystrokes) and interprets them through the getline() function. It then recognizes the input as a string and stores it in a buffer of corresponding size. Then it has to begin digesting this input, separating it into smaller (byte-size if you will) parts that are easier for it to understand. It does so by a process we call tokenization which is, simply put, separating the input into it’s constituent parts. It takes a larger string and divides it into however many elements are separated by a given character (in this case it’s a blank space though you can program the separator to be whatever you want). In our case it would be interpreted as two separate tokens:
Token 0 = ‘ls’
Token 1 = ‘-l’
Phase two of interpretation is to decide what kind of command is being passed: built-in or executable. Without diving too deep into it built-in commands are initialized on shell start-up and allow the basics of the shell to run. Meanwhile executables are more specific-use commands. They aren’t as frequently called as built-ins and hence the computer has to go look for wherever they’re stored in order to perform them. Since our list command is not a built-in, enter phase two: the PATH.
PATH is an environment variable that contains the absolute path for all executable files, each one separated by a semicolon (:). It will recursively search for the command in question. Once it finds our ls command it will load it to memory and invoke a system call fork (). This separates our parent process (the shell) and our newly created child process (ls). Finally our child process invokes the system call execve() which creates a process to run our ls command. Once it has finished doing it’s ls thing in the most -l of fashions it will call on exit() to return a 0 if everything went well and free up any temporarily used memory. As a final goodbye our process will prompt us with a new line and a $ to signal it is ready to receive a new input from us and do it all over again.
I hope I was able to to prove to you just how stupid computers are. After all, it took an entire team of very clever people a very long time to come up with everything I’ve described in this short blog in order to perform such simple tasks as listing the contents of the working directory. What I’m trying to say is appreciate the programmers that came before us, we truly are standing on the shoulders of giants!