Tools: Latest: Did you know that a shell is just an ordinary program?

Tools: Latest: Did you know that a shell is just an ordinary program?

Many people think that the command line (bash, zsh) where we type our commands is some deeply integrated, magical part of the operating system. In reality, a shell is just the starting program on your server. When you log in via SSH, the system looks into the user's configuration file and runs whatever is specified there. If you replace /bin/bash with the path to Python, you'll end up in the Python interpreter. If you specify /usr/bin/top, the task manager will open, and exiting it will immediately close your SSH session. But how does a shell work under the hood? Fundamentally, it's just an infinite loop. Here is what its minimalist skeleton looks like in modern C. Minimal Shell (based on the UNIX pattern) How it works under the hood The entire essence of a classic UNIX shell boils down to the fork-exec-wait pattern, which can be described in five simple steps: What is the "magic of fork"? After calling the fork() function, the operating system clones the process. Both processes continue execution from the exact same line of code (the return from the fork function). They differ in only one way: in the child process, the function returns 0, while the parent receives the ID of the newly created clone. This value acts as a fork in the road, forcing the processes down different branches of the if-else statement. This code is intentionally bare-bones and serves purely educational purposes. It doesn't know how to handle parameters (a command like ls -l will throw an error because the system will look for a single executable file with a space in its name), and it doesn't support built-in commands like cd or exit, nor pipes. A real shell does a massive amount of work tokenizing your input string and configuring file descriptors before calling exec. Nevertheless, absolutely every terminal is built on top of this elegant mechanism. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to ? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Code Block

Copy

/usr/bin/top #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/wait.h> #define MAXLINE 4096 int main(void) { char buf[MAXLINE]; printf("%% "); while (fgets(buf, MAXLINE, stdin) != NULL) { size_t len = strlen(buf); if (len > 0 && buf[len - 1] == '\n') { buf[len - 1] = '\0'; } pid_t pid = fork(); if (pid < 0) { perror("fork error"); exit(EXIT_FAILURE); } else if (pid == 0) { /* child process */ execlp(buf, buf, (char *)NULL); /* if execlp returns control, then an error occurred. */ fprintf(stderr, "couldn't execute: %s\n", buf); exit(127); } /* parent process */ int status; if (waitpid(pid, &status, 0) < 0) { perror("waitpid error"); exit(EXIT_FAILURE); } printf("%% "); } exit(EXIT_SUCCESS); } #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/wait.h> #define MAXLINE 4096 int main(void) { char buf[MAXLINE]; printf("%% "); while (fgets(buf, MAXLINE, stdin) != NULL) { size_t len = strlen(buf); if (len > 0 && buf[len - 1] == '\n') { buf[len - 1] = '\0'; } pid_t pid = fork(); if (pid < 0) { perror("fork error"); exit(EXIT_FAILURE); } else if (pid == 0) { /* child process */ execlp(buf, buf, (char *)NULL); /* if execlp returns control, then an error occurred. */ fprintf(stderr, "couldn't execute: %s\n", buf); exit(127); } /* parent process */ int status; if (waitpid(pid, &status, 0) < 0) { perror("waitpid error"); exit(EXIT_FAILURE); } printf("%% "); } exit(EXIT_SUCCESS); } #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/wait.h> #define MAXLINE 4096 int main(void) { char buf[MAXLINE]; printf("%% "); while (fgets(buf, MAXLINE, stdin) != NULL) { size_t len = strlen(buf); if (len > 0 && buf[len - 1] == '\n') { buf[len - 1] = '\0'; } pid_t pid = fork(); if (pid < 0) { perror("fork error"); exit(EXIT_FAILURE); } else if (pid == 0) { /* child process */ execlp(buf, buf, (char *)NULL); /* if execlp returns control, then an error occurred. */ fprintf(stderr, "couldn't execute: %s\n", buf); exit(127); } /* parent process */ int status; if (waitpid(pid, &status, 0) < 0) { perror("waitpid error"); exit(EXIT_FAILURE); } printf("%% "); } exit(EXIT_SUCCESS); } - Read a line of input from the user in a loop. - Strip the newline character left after pressing Enter. - Fork the process — ask the OS kernel to create an exact copy of our program. - If it's the child process, replace ourselves with the program whose path the user entered. - If it's the parent (our original shell), simply wait for the child to finish, and then print the % prompt again.