From d722c601e8a80279027aa2988dc5fcd371ee9d4d Mon Sep 17 00:00:00 2001 From: Abdellah El Morabit Date: Sun, 2 Feb 2025 20:00:50 +0100 Subject: layout the plans for making the shell, i understand the idea a little better now, i think i implemented an exit function, pretty sure it works decently well, current next thing im going to work on is understanding readline, the autocompletion that comes with it, and trying to list a directory which i have no clue how im going to do --- main.c | 38 ++++++++----------- project.md | 123 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+), 22 deletions(-) create mode 100644 project.md diff --git a/main.c b/main.c index 49b0f0b..c0395e6 100644 --- a/main.c +++ b/main.c @@ -4,6 +4,9 @@ #include #include #include +#include +#include + /* BOOLEANS */ #define true 1 @@ -14,38 +17,30 @@ typedef struct int process_id; } id; -void process_execution(int id) -{ - fork(); -} -void REDIRECTION(); -void SCRIPTING(); - -void ERROR_QUIT(char *CURRENT_COMMAND) -{ - printf("failed %s", CURRENT_COMMAND); - -} - -void err_sys(char *string) +void exit_program(char *argv) { - printf("%s", string); + if (strcmp(argv, "exit")) + { + exit(0); + } } -void PROCESS_PID() +void get_pid_parent_process() { - printf("PID %ld\n", (long)getpid()); } void list_directories() { + } + char *save_variable(char* command, char *argv) { char *variable = (char *)malloc(sizeof(char)); + char *input = readline(); if (strcmp(argv[0], "$") == 0) { @@ -56,7 +51,7 @@ char *save_variable(char* command, char *argv) } } -void ECHO_INPUT(char *command, char *argv) +void echo(char *command, char *argv) { if (strcmp(command, "echo") == 0) { @@ -64,10 +59,9 @@ void ECHO_INPUT(char *command, char *argv) } } -int main(int argc, char *argv[]) { - - int i = getpid(); - printf("%d", i); +int main(int argc, char *argv[]) +{ + return 0; } diff --git a/project.md b/project.md new file mode 100644 index 0000000..d8561cf --- /dev/null +++ b/project.md @@ -0,0 +1,123 @@ +### **Building a Unix Shell from Scratch** +When building a Unix shell from scratch, you typically create a **command-line interface (CLI)** that can: +1. Read user input (commands). +2. Parse the input (tokenizing, handling arguments, redirections, etc.). +3. Execute the command, either internally (built-in commands) or by forking a new process. +4. Handle job control (foreground/background processes, signals). +5. (Optionally) Support scripting, aliases, and advanced features like pipes (`|`) and I/O redirection (`>`, `<`). + +A minimal shell implementation in C follows these steps: + +#### **1. Read User Input** +Use `getline()` or `read()` to accept input from the terminal. + +#### **2. Tokenize Input* +convert into **tokens** (command and arguments) using `strtok()`. + +#### **3. Execute the Command** +- If it is a built-in command (e.g., `cd`, `exit`), handle it directly. +- Otherwise, use `fork()` to create a child process and `execvp()` to execute the command. + +#### **4. Handle Process Management** +- Use `waitpid()` to wait for processes. +- Implement background jobs (`&` support). +- Signal handling (`SIGCHLD`, `SIGINT` for `Ctrl+C`). + +#### **5. Optional Features** +- Environment variables handling (`export`, `$PATH` resolution). +- File redirections (`>` for output, `<` for input). +- Pipe (`|`) execution with `pipe()`, `dup2()`, and multiple processes. + +Here’s a **basic Unix shell** in C: + +```c +#include +#include +#include +#include +#include +#include + +#define MAX_CMD_LEN 1024 +#define MAX_ARGS 64 + +void execute_command(char **args) { + pid_t pid = fork(); + if (pid == 0) { // Child process + execvp(args[0], args); + perror("exec failed"); + exit(1); + } else if (pid > 0) { // Parent process + wait(NULL); + } else { + perror("fork failed"); + } +} + +int main() { + char input[MAX_CMD_LEN]; + char *args[MAX_ARGS]; + + while (1) { + printf("mysh> "); + if (!fgets(input, MAX_CMD_LEN, stdin)) break; // Read input + + input[strcspn(input, "\n")] = 0; // Remove newline + + // Tokenize input + int i = 0; + args[i] = strtok(input, " "); + while (args[i] && i < MAX_ARGS - 1) { + args[++i] = strtok(NULL, " "); + } + args[i] = NULL; // NULL-terminate args + + if (args[0]) { + if (strcmp(args[0], "exit") == 0) break; // Built-in exit + execute_command(args); + } + } + return 0; +} +``` + +This basic shell: +- Reads input +- Tokenizes it +- Forks a process and executes commands using `execvp()` +- Waits for processes to complete +- Supports built-in `exit` command + +A full-featured shell would include: +- **Job control** (`fg`, `bg`, `jobs`) +- **Signal handling** (`SIGTSTP`, `SIGINT`) +- **Redirections** (`>`, `<`) +- **Pipelines** (`|`) +- **Shell scripting** (`if`, `for`, `while` loops) + +--- + +### **Unix Shell vs. POSIX Shell** +A **Unix shell** is a general term for any command interpreter that runs on Unix-like systems (e.g., **bash, zsh, ksh, tcsh, dash**). + +A **POSIX shell** is a shell that strictly conforms to the **POSIX standard (IEEE 1003.1-2008)**, ensuring portability across Unix systems. The **POSIX shell specification** is based on **Bourne Shell (sh)** and defines: +- **Syntax** (`if`, `for`, `case`, `while`, etc.) +- **Built-in commands** (`cd`, `echo`, `export`, `set`, etc.) +- **I/O redirection** (`>`, `<`, `2>`, `&>`, etc.) +- **Substitution** (`$()`, `` ` ``, `${VAR}`) + +**Key Differences:** +| Feature | Unix Shell (Bash, Zsh, etc.) | POSIX Shell (Dash, sh) | +|---------|-----------------|------------------| +| Scripting Extensions | Yes (arrays, `[[ ]]`, associative arrays, `printf -v`, etc.) | No, follows strict POSIX | +| Performance | Slower due to extra features | Faster (used for system scripts) | +| Compatibility | Linux, BSD, Mac, Solaris | Strictly portable | +| Interactive Features | Yes (command history, autocomplete, themes) | Minimal | + +For example: +- **Bash (`bash`)** is a Unix shell but has extra non-POSIX features (`[[` test, associative arrays). +- **Dash (`/bin/dash`)** is a strict POSIX shell, often used for system scripts due to speed. + +If you want to write **portable shell scripts**, stick to **POSIX sh** (e.g., `#!/bin/sh` instead of `#!/bin/bash`). + +Would you like help adding features like redirections or pipes to the basic shell? 🚀 -- cgit v1.2.3-70-g09d2