
Minishell
Unix Shell Implementation
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
Basic Shell Structure
Implemented the fundamental shell loop with command reading, basic parsing, and simple command execution without advanced features.
Built-in Commands
Developed essential built-in commands including echo, cd, pwd, export, unset, env, and exit with proper option handling and error management.
Advanced Parsing
Implemented advanced parsing features including quote handling, variable expansion, and complex command parsing with proper tokenization.
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
Implementing a robust parser that handles quotes, escape sequences, variable expansion, and complex command structures accurately.
Developed a multi-stage parsing system with tokenization, quote handling, and variable expansion phases for accurate command interpretation.
Process Management
Managing child processes, handling process communication, and ensuring proper cleanup of resources and zombie processes.
Implemented proper fork/exec patterns with waitpid for process management and signal handling for clean process termination.
I/O Redirection
Implementing complex I/O redirection with pipes, file redirection, and maintaining proper file descriptor management.
Created a systematic approach to I/O redirection using dup2 system calls and proper file descriptor management with error handling.
// 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);
}