What happens when you type ls -l in the shell
Sofía García
Backend Engineer | HubSpot Developer @ Mole Street | HubSpot Elite Partner
To begin with, you should know that the Unix shell is. It is a command interpreter that provides a command-line user interface for Unix-like operating systems.
The shell is both, an interactive command language and a scripting language, and is used by the operating system to control the execution of the system using shell scripts, and by the users, typically, using a terminal emulator.
How do we figure out where to find the shell? Just as we know that the Kernel is the most internal part of the operating system, which allows communication between hardware and software and works as a nucleus for the computer; The shell is the outermost part of the operating system that allows the execution of the system through shell scripts, this being possible in an interactive or non-interactive way.
As I told you before, the shell is accessed by the terminal through which it is run and, as the first instance, we receive the command prompt which is usually a "$" symbol. In a short version, we could say a shell does three main things in its lifetime: Initialize, interpret, and terminate.
Basically, when we initialize in the shell, it shows us the respective command prompt which will be constantly waiting to receive a command in the standard input. Once it receives it, the shell reads this command, then continues with the execution process which includes the creation of a child process that allows it, later, in the first place, to divide the line of arguments passed (since the user can pass more than just one command) and to this extent, the shell by dividing the passed line according to each space it finds; can determine how many commands have been passed in the command line interpreter and thus proceed with the search for those executable files that allow to carry out the function of the command or commands passed.
Reading the passed line
At this point, the shell has already printed its command prompt in the standard output and is waiting for a command to pass. When receiving it, it plays a fairly important role the getline function.
What getline() function does? Reads an entire line from the stream, storing the address of the buffer containing the text into *lineptr. The buffer is null-terminated and includes the newline character if one was found. If *lineptr is set to NULL and *n is set 0 before the call, then getline() will allocate a buffer for storing the line. This buffer should be freed by the user program even if getline() failed. Alternatively, before calling getline(), *lineptr can contain a pointer to a malloc - allocated buffer *n bytes in size. If the buffer is not large enough to hold the line, getline() resizes it with realloc, updating *lineptr and *n as necessary.
On success, getline() return the number of characters read, including the delimiter character, but not including the terminating null byte ('\0'). This value can be used to handle embedded null bytes in the line read. Both functions return -1 on failure to read a line (including end-of-file condition). In the event of an error, errno is set to indicate the cause.
Why all this process and why the re-allocating memory? Sadly, because you can not know how many arguments the user is going to pass to the command line. You can’t simply allocate a block and hope they don’t exceed it. Instead, you need to start with a block, and if they do exceed it, reallocate with more space.
Parsing the line
Once the shell already read the passing line and saved it, go to the next step: parsing the line (or dividing the line into a list of arguments). How? Tokenizing the string using whitespace as delimiters; all this through the strtok function.
The strtok() function breaks a string into a sequence of zero or more nonempty tokens. On the first call to strtok(), the string to be parsed should be specified in str. In each subsequent call that should parse the same string, str must be NULL. The delim argument specifies a set of bytes that delimit the tokens in the parsed string. The caller may specify different strings in delim in successive calls that parse the same string. Each call to strtok() returns a pointer to a null-terminated string containing the next token. This string does not include the delimiting byte. If no more tokens are found, strtok() returns NULL.
What strtok() actually does is return pointers to within the given string, and place \0 bytes at the end of each token. Each pointer is stored in an array of character pointers. Finally, an a reallocation of the array of pointers is done if necessary. The process repeats until no token is returned by strtok, at which point we null-terminate the list of tokens.
At this point, there is an array of tokens, ready to execute.
Process and execution
Starting processes is the main function of shells. One of the most practical ways for processes to get started is the fork() system call.
When this function is called, the operating system makes a duplicate of the process and starts them both running. The original process is called the “parent”, and the new one is called the “child”. fork() returns 0 to the child process, and it returns to the parent the process ID number (PID) of its child. In essence, this means that the only way for new processes is to start is by an existing one duplicating itself.
Typically, when you want to run a new process, you don’t just want another copy of the same program – you want to run a different program. That’s what the execve() system call is all about. It replaces the current running program with an entirely new one. This means that when you call execve, the operating system stops your process, loads up the new program, and starts that one in its place. A process never returns from an exec() call (unless there’s an error).
With these two system calls, we have a closer idea to how the shell works. First, an existing process forks itself into two separate ones. Then, the child uses execve() to replace itself with a new program. The parent process can continue doing other things, and it can even keep tabs on its children, using the system call wait().
System call wait, as its manual says, is used to wait for state changes in a child of the calling process, and obtain information about the child whose state has changed. A state change is considered to be: the child terminated; the child was stopped by a signal, or the child was resumed by a signal. In the case of a terminated child, performing a wait allows the system to release the resources associated with the child; if a wait is not performed, then the terminated child remains in a "zombie" state. The wait() system call suspends execution of the calling thread until one of its children terminates. The call wait(&wstatus) is equivalent to: waitpid(-1, &wstatus, 0).
How does the shell proceed with ls -l?
The above would then be the process that the shell performs as the user passes a command to it. To answer what happens when we pass ls -l to it, we must, at this point, know what the respective command does. The process as such, and already explained, is simple. The shell reads it, analyzes it, divides it, creates a child process that will allow it to continue running as a parent and wait for the expected process to be carried out in its child, it will search, in the middle of that process, for the executable of the command passed and the flag which in this case is '-l' and, if it finds it, it will execute it, otherwise, it will show us the error "no such file or directory".
The 'ls' command list information about the FILEs (the current directory by default). Sort entries alphabetically if non order is specified. You can find the executable of ls at bin directory .
The '-l' flag is used for a long listing format. e.g:
I hope this helps you. A bit summarized being part of the final project of the first trimester at Holberton School. Research and writing by Cristhian Carbonell and Diana Sofía García.
Software Developer & Mechanical Engineer
4 年Does it display a long format list?
Backend Engineer | HubSpot Developer @ Mole Street | HubSpot Elite Partner
4 年Cristhian Carbonell