This project is a custom Unix shell implementation, designed to mimic the behavior of a standard shell with support for various command-line operations, including pipelines, input/output redirection, background execution, and conditional command execution.
- Command Execution: Supports execution of simple commands, pipelines, and conditional commands (
&&,||). - Input/Output Redirection: Supports input redirection (
<), output redirection (>), and appending output (>>). - Background Execution: Commands can be executed in the background using the
&operator. - Command Grouping: Commands can be grouped using parentheses
()to execute them in a subshell. - Pipeline Support: Supports command pipelines (
|) where the output of one command is passed as input to the next. - Built-in
cdCommand: Includes a built-incdcommand for changing directories. - Error Handling: Properly handles errors and ensures no zombie processes are left after command execution.
The project is built using a Makefile.
Assembling and executing:
make runOnce the shell is running, you can enter commands just like you would in a standard Unix shell. Below are some examples of supported operations:
- Bash
ls -l- Pipeline
ls | grep .txt- Input Redirection
wc -l < input.txt- Output Redirection
ls > output.txt- Appending Output
echo "Hello, World!" >> output.txt- Background Execution
sleep 10 &- Conditional Execution
ls && echo "Command succeeded"
ls || echo "Command failed"- Command Grouping with Parentheses
(cd /tmp; ls)- Built-in cd Command
cd /path/to/directoryThe project is implemented using an Abstract Syntax Tree (AST) to parse and execute commands. The AST is a tree representation of the syntactic structure of the command line input, allowing for efficient parsing and execution of complex commands, including pipelines, redirections, and conditional operations.
Here’s an overview of the key files and their roles in the project:
-
ast.candast.h: These files define the structure and functions for building and manipulating the AST. The AST nodes represent different types of commands (e.g., simple commands, pipelines, redirections, etc.), and the tree is constructed during the parsing phase. -
exec.candexec.h: These files contain the logic for traversing and executing the AST. The execution engine walks the AST and performs the appropriate actions for each node, such as executing commands, handling pipelines, and managing input/output redirections. -
lexer.candlexer.h: The lexer is responsible for tokenizing the input command line. It breaks the input string into tokens (e.g., command names, operators, filenames) that are used by the parser to build the AST. -
parse.candparse.h: The parser takes the tokens generated by the lexer and constructs the AST. It handles the grammar of the shell, including command grouping, pipelines, and conditional execution. -
nodes.h: This file defines the structures for the AST nodes. Each node type corresponds to a specific command or operation (e.g., simple command, pipeline, redirection). -
shell_main.c: The main entry point of the shell. It initializes the shell, reads input from the user, and coordinates the lexing, parsing, and execution phases. -
tokens.h: Defines the token types used by the lexer and parser. Tokens represent the basic building blocks of the command line input (e.g., command names, operators, filenames).
The AST is central to the shell's operation. Here’s how it works:
- Lexing: The input command line is tokenized by the lexer, which produces a stream of tokens.
- Parsing: The parser reads the tokens and constructs the AST based on the shell's grammar. For example:
- A simple command like
ls -lbecomes a single AST node. - A pipeline like
ls | grep .txtbecomes a parent node with two child nodes representing the commandslsandgrep. - Redirections like
ls > output.txtare represented as nodes with additional information about the input/output files.
- A simple command like
- Execution: The executor traverses the AST and executes the commands. For pipelines, it sets up the necessary pipes and forks processes to handle each command in the pipeline. For redirections, it manages file descriptors to redirect input/output.
This AST-based approach ensures that the shell can handle complex command structures efficiently and provides a clear separation between parsing and execution.
Thanks for checking :)