Minishell
42 Abu Dhabi

Minishell

Unix Shell Implementation

2 months
2
CUnix System CallsProcess ManagementSignal HandlingFile Descriptors

In the Minishell project at 42 school, I developed a simplified version of a Unix shell. This involved implementing various functionalities like executing commands, managing environment variables, handling signals, and performing input/output redirection. By creating Minishell, I gained hands-on experience in system programming and process management within Unix/Linux environments. This project challenged me to design an efficient and user-friendly command-line interface while adhering to Unix shell standards and conventions.

Key Features

Command Execution

Execute system commands and programs with proper argument parsing, PATH resolution, and process creation using fork and exec system calls.

I/O Redirection

Complete implementation of input/output redirection with support for <, >, >>, and | operators for flexible command chaining and file operations.

Built-in Commands

Implementation of essential shell built-ins including echo, cd, pwd, export, unset, env, and exit with proper option handling and error management.

Signal Handling

Proper signal management for SIGINT, SIGQUIT, and SIGTERM with appropriate signal handling for both shell and child processes.

Environment Variables

Complete environment variable management with support for variable expansion, modification, and inheritance to child processes.

Quote Parsing

Advanced parsing system handling single quotes, double quotes, and escape sequences for proper command interpretation and execution.

Development Journey

Phase 1

Basic Shell Structure

Implemented the fundamental shell loop with command reading, basic parsing, and simple command execution without advanced features.

Phase 2

Built-in Commands

Developed essential built-in commands including echo, cd, pwd, export, unset, env, and exit with proper option handling and error management.

Phase 3

Advanced Parsing

Implemented advanced parsing features including quote handling, variable expansion, and complex command parsing with proper tokenization.

Phase 4

I/O & Signals

Added I/O redirection support and comprehensive signal handling for a complete shell experience with proper resource management.

Challenges & Solutions

Command Parsing Complexity

Problem:

Implementing a robust parser that handles quotes, escape sequences, variable expansion, and complex command structures accurately.

Solution:

Developed a multi-stage parsing system with tokenization, quote handling, and variable expansion phases for accurate command interpretation.

Process Management

Problem:

Managing child processes, handling process communication, and ensuring proper cleanup of resources and zombie processes.

Solution:

Implemented proper fork/exec patterns with waitpid for process management and signal handling for clean process termination.

I/O Redirection

Problem:

Implementing complex I/O redirection with pipes, file redirection, and maintaining proper file descriptor management.

Solution:

Created a systematic approach to I/O redirection using dup2 system calls and proper file descriptor management with error handling.

minishell.c
c
// Command parsing and execution structure
typedef struct s_cmd
{
    char    **args;
    char    *infile;
    char    *outfile;
    int     append;
    int     pipe_fd[2];
    struct s_cmd *next;
} t_cmd;

// Execute command with proper I/O redirection
int execute_command(t_cmd *cmd, char **envp)
{
    pid_t   pid;
    int     status;
    
    if (is_builtin(cmd->args[0]))
        return (execute_builtin(cmd));
    
    pid = fork();
    if (pid == 0)
    {
        // Child process: setup I/O redirection
        if (cmd->infile)
            setup_input_redirection(cmd->infile);
        if (cmd->outfile)
            setup_output_redirection(cmd->outfile, cmd->append);
        if (cmd->next)
            setup_pipe(cmd->pipe_fd);
            
        // Execute the command
        execve(get_command_path(cmd->args[0]), cmd->args, envp);
        perror("execve failed");
        exit(1);
    }
    else if (pid > 0)
    {
        // Parent process: wait for child
        waitpid(pid, &status, 0);
        return (WEXITSTATUS(status));
    }
    return (-1);
}

// Built-in command: change directory
int builtin_cd(char **args)
{
    char *path;
    char cwd[PATH_MAX];
    
    if (!args[1])
        path = getenv("HOME");
    else
        path = args[1];
        
    if (chdir(path) != 0)
    {
        perror("cd");
        return (1);
    }
    
    // Update PWD environment variable
    if (getcwd(cwd, sizeof(cwd)))
        setenv("PWD", cwd, 1);
        
    return (0);
}