Skip to content

MostafaAbdulazziz/Mini-Shell

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

2 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Mini Shell

A comprehensive Unix-like shell implementation in C++ featuring command parsing, I/O redirection, pipes, background processes, and wildcard expansion.


πŸ“‹ Table of Contents


🎯 Overview

This mini shell is a command-line interpreter that provides core shell functionality similar to bash or sh. It parses user commands, handles I/O redirection and piping, manages processes, and supports advanced features like wildcard expansion.

Key Capabilities

  • Execute system commands with arguments
  • Input/Output/Error redirection
  • Command piping (chaining multiple commands)
  • Background process execution
  • Built-in commands (cd, exit)
  • Signal handling (Ctrl-C protection)
  • Process logging
  • Wildcard expansion (*, ?)

πŸ“ Project Structure

mini-shell/
β”œβ”€β”€ command.h          # Command data structures
β”œβ”€β”€ command.cc         # Core implementation (parsing & execution)
β”œβ”€β”€ tokenizer.h        # Token type definitions
β”œβ”€β”€ tokenizer.cc       # Lexical analysis (tokenization)
β”œβ”€β”€ Makefile          # Build configuration
β”œβ”€β”€ log.txt           # Process termination log (created at runtime)
└── testcases/        # Test scripts
    β”œβ”€β”€ basic_ls_al.sh
    β”œβ”€β”€ echo_hello_os_lab.sh
    β”œβ”€β”€ redirect_create_output1.sh
    └── ... (10 test scripts total)

File Hierarchy & Responsibilities

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚           main() - Entry Point              β”‚
β”‚  - Signal handlers setup (SIGINT, SIGCHLD)  β”‚
β”‚  - Log file initialization                  β”‚
β”‚  - Main input loop                          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                  β”‚
                  β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚         tokenizer.cc / tokenizer.h          β”‚
β”‚  - Lexical analysis of input string         β”‚
β”‚  - Converts input β†’ vector<Token>           β”‚
β”‚  - Recognizes operators: | > >> < 2> >>& &  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                  β”‚
                  β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚          parse() - Syntax Analysis          β”‚
β”‚  - Builds Command & SimpleCommand structs   β”‚
β”‚  - Handles redirection operators            β”‚
β”‚  - Handles pipe operators                   β”‚
β”‚  - Expands wildcards                        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                  β”‚
                  β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚     Command::execute() - Execution Engine   β”‚
β”‚  - Built-in commands (cd, exit)             β”‚
β”‚  - Process creation (fork/exec)             β”‚
β”‚  - File descriptor management               β”‚
β”‚  - Pipe construction                        β”‚
β”‚  - Process waiting & logging                β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ—οΈ Architecture & Workflow

1. Data Structures (command.h)

SimpleCommand

Represents a single command with its arguments.

struct SimpleCommand {
    int _numberOfArguments;
    char **_arguments;  // NULL-terminated array
    // Methods: insertArgument()
}

Example: ls -al β†’ _arguments = ["ls", "-al", NULL]

Command

Represents a complete shell command (may contain multiple SimpleCommands connected by pipes).

struct Command {
    SimpleCommand **_simpleCommands;  // Array of simple commands
    int _numberOfSimpleCommands;
    char *_outFile;      // Output redirection file
    char *_inputFile;    // Input redirection file
    char *_errFile;      // Error redirection file
    int _background;     // Background flag
    int _append;         // Append mode flag
    int _out_error;      // Redirect stderr to stdout flag
    // Methods: execute(), print(), clear(), prompt()
}

Example: ls -al | grep txt > out.txt creates:

  • 2 SimpleCommands: ["ls", "-al"] and ["grep", "txt"]
  • _outFile = "out.txt"

2. Tokenization (tokenizer.cc)

Token Types (tokenizer.h)

enum TokenType {
    TOKEN_COMMAND,              // First word in command
    TOKEN_ARGUMENT,             // Arguments and filenames
    TOKEN_REDIRECT,             // >
    TOKEN_APPEND,               // >>
    TOKEN_INPUT,                // <
    TOKEN_ERROR,                // 2>
    TOKEN_PIPE,                 // |
    TOKEN_BACKGROUND,           // &
    TOKEN_REDIRECT_AND_ERROR,   // >>&
    TOKEN_EOF                   // End of input
}

Tokenization Process

Input: "ls -al | grep txt > output.txt"
         ↓
Tokenizer splits by whitespace and identifies operators
         ↓
Tokens: [
  {TOKEN_COMMAND, "ls"},
  {TOKEN_ARGUMENT, "-al"},
  {TOKEN_PIPE, "|"},
  {TOKEN_COMMAND, "grep"},
  {TOKEN_ARGUMENT, "txt"},
  {TOKEN_REDIRECT, ">"},
  {TOKEN_ARGUMENT, "output.txt"},
  {TOKEN_EOF, ""}
]

3. Parsing (command.cc::parse())

Parsing Algorithm

  1. Initialize: Create empty Command and SimpleCommand
  2. Iterate tokens:
    • Commands/Arguments: Add to current SimpleCommand (with wildcard expansion)
    • Pipe |: Finalize current SimpleCommand, start new one
    • Redirection >, >>, <, 2>, >>&: Mark next token as filename
    • Background &: Set background flag
  3. Finalize: Add last SimpleCommand to Command
  4. Execute: Call Command::execute()

Example Parsing Flow

Input: "cat < input.txt | grep error > output.txt"

Parse Steps:
1. "cat"          β†’ Create SimpleCommand, add "cat"
2. "<"            β†’ Set expectingFilename = true, redirectType = INPUT
3. "input.txt"    β†’ Set _inputFile = "input.txt"
4. "|"            β†’ Insert SimpleCommand #1, create new SimpleCommand
5. "grep"         β†’ Add "grep" to SimpleCommand #2
6. "error"        β†’ Add "error" to SimpleCommand #2
7. ">"            β†’ Set expectingFilename = true, redirectType = REDIRECT
8. "output.txt"   β†’ Set _outFile = "output.txt"
9. EOF            β†’ Insert SimpleCommand #2, call execute()

Result Command:
- SimpleCommand[0]: ["cat"]
- SimpleCommand[1]: ["grep", "error"]
- _inputFile: "input.txt"
- _outFile: "output.txt"

4. Execution (command.cc::Command::execute())

Execution Workflow

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 1. Check for Built-in Commands     β”‚
β”‚    - exit β†’ Quit shell              β”‚
β”‚    - cd   β†’ Change directory        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚ (External command)
             β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 2. Save Default File Descriptors   β”‚
β”‚    defaultin  = dup(0)              β”‚
β”‚    defaultout = dup(1)              β”‚
β”‚    defaulterr = dup(2)              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚
             β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 3. Setup Input Redirection          β”‚
β”‚    if (_inputFile)                  β”‚
β”‚      fdin = open(_inputFile, RD)    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚
             β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 4. FOR EACH SimpleCommand:          β”‚
β”‚                                     β”‚
β”‚  A. Redirect stdin                  β”‚
β”‚     dup2(fdin, 0)                   β”‚
β”‚                                     β”‚
β”‚  B. Setup Output:                   β”‚
β”‚     - If LAST command:              β”‚
β”‚       * _outFile β†’ open file        β”‚
β”‚       * _errFile β†’ open error file  β”‚
β”‚     - Else (not last):              β”‚
β”‚       * Create pipe()               β”‚
β”‚       * fdout = pipe[1]             β”‚
β”‚       * fdin  = pipe[0] (for next)  β”‚
β”‚                                     β”‚
β”‚  C. Redirect stdout & stderr        β”‚
β”‚     dup2(fdout, 1)                  β”‚
β”‚     dup2(fderr, 2)                  β”‚
β”‚                                     β”‚
β”‚  D. Fork & Execute                  β”‚
β”‚     pid = fork()                    β”‚
β”‚     if (pid == 0)                   β”‚
β”‚       execvp(command, args)         β”‚
β”‚                                     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚
             β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 5. Restore Default File Descriptorsβ”‚
β”‚    dup2(defaultin, 0)               β”‚
β”‚    dup2(defaultout, 1)              β”‚
β”‚    dup2(defaulterr, 2)              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚
             β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 6. Wait for Process (if foreground) β”‚
β”‚    waitpid(last_pid, &status, 0)    β”‚
β”‚    Log termination to log.txt       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Process Management

  • Foreground: Shell waits using waitpid() and logs termination
  • Background: Shell doesn't wait; SIGCHLD handler logs when process finishes

✨ Features

Core Features

1. Command Execution

myshell> ls -al
myshell> pwd
myshell> echo "Hello World"

2. I/O Redirection

# Output redirection (overwrite)
myshell> ls -al > output.txt

# Output redirection (append)
myshell> echo "new line" >> output.txt

# Input redirection
myshell> cat < input.txt

# Error redirection
myshell> ls nonexistent 2> error.txt

# Redirect both stdout and stderr
myshell> command >>& output.txt

3. Pipes

# Single pipe
myshell> ls -al | grep txt

# Multiple pipes
myshell> cat file.txt | grep error | wc -l

# Pipes with redirection
myshell> ls -al | grep txt > results.txt

4. Background Processes

myshell> sleep 10 &
myshell> long_running_command &

The shell returns immediately; process runs in background.

5. Built-in Commands

cd - Change Directory
myshell> cd /tmp              # Change to /tmp
myshell> cd ..                # Go up one directory
myshell> cd                   # Go to HOME directory
myshell> cd ~                 # Go to HOME directory

Output:

Changing to directory '/tmp'
You are now in /tmp
exit - Quit Shell
myshell> exit
Good bye!!

6. Signal Handling

  • Ctrl-C (SIGINT): Shell ignores it; prints newline and new prompt
  • SIGCHLD: Automatically logs child process terminations

7. Process Logging

Every terminated child process is logged to log.txt:

[Sun Oct 26 14:23:45 2025] Child process 12345 terminated with status 0
[Sun Oct 26 14:23:46 2025] Child process 12346 terminated with status 0

Bonus Feature: Wildcard Expansion

* - Matches Zero or More Characters

myshell> echo *               # All files in current directory
myshell> echo *.cc            # All .cc files
myshell> echo test*.txt       # Files starting with "test", ending with ".txt"
myshell> ls /tmp/*            # All files in /tmp

? - Matches Exactly One Character

myshell> echo file?.txt       # file1.txt, file2.txt, fileA.txt
myshell> ls test??.cc         # test01.cc, test23.cc

Combined Wildcards

myshell> echo M*f*            # Files starting with M, containing f
myshell> ls /*bin*/ls         # /bin/ls, /usr/bin/ls, etc.

Implementation: Uses glob() from <glob.h>, results are sorted alphabetically.


πŸ”§ Building & Running

Prerequisites

  • Compiler: g++ with C++11 support
  • OS: Linux/Unix-based system
  • Libraries: Standard C++ library, POSIX APIs

Build Commands

Compile the Shell

make

Output: Creates myshell executable

Clean Build Artifacts

make clean

Removes: *.o, myshell, log.txt

Rebuild from Scratch

make rebuild

Equivalent to: make clean && make

Compile and Run

make run

Equivalent to: make && ./myshell

Running the Shell

Start the Shell

./myshell

Output:

myshell>

Interactive Usage

myshell> ls -al
myshell> cat file.txt | grep pattern > results.txt
myshell> exit
Good bye!!

Scripted Input

echo -e "ls\npwd\nexit" | ./myshell

πŸ“ Usage Examples

Example 1: Basic Command with Output Redirection

myshell> ls -al > files.txt

What happens:

  1. Tokenize: ["ls", "-al", ">", "files.txt"]
  2. Parse: Create SimpleCommand ["ls", "-al"], set _outFile = "files.txt"
  3. Execute: Fork β†’ Open files.txt for writing β†’ Redirect stdout β†’ execvp("ls", ["ls", "-al"])

Example 2: Pipeline with Multiple Commands

myshell> cat data.txt | grep ERROR | wc -l

What happens:

  1. Parse: 3 SimpleCommands: ["cat", "data.txt"], ["grep", "ERROR"], ["wc", "-l"]
  2. Execute:
    • Fork β†’ cat data.txt β†’ stdout β†’ pipe1
    • Fork β†’ pipe1 β†’ stdin, grep ERROR, stdout β†’ pipe2
    • Fork β†’ pipe2 β†’ stdin, wc -l, stdout β†’ terminal

Example 3: Background Process

myshell> sleep 5 &

What happens:

  1. Parse: Set _background = 1
  2. Execute: Fork β†’ execvp("sleep", ["sleep", "5"]) β†’ Don't wait, return to prompt immediately
  3. SIGCHLD handler logs when sleep finishes

Example 4: Wildcard Expansion

myshell> echo *.cc

What happens:

  1. Tokenize: ["echo", "*.cc"]
  2. Parse: Call expandWildcard("*.cc") β†’ Returns ["command.cc", "tokenizer.cc"]
  3. SimpleCommand: ["echo", "command.cc", "tokenizer.cc"]
  4. Execute: Prints command.cc tokenizer.cc

πŸ§ͺ Testing

Test Suite

Located in testcases/ directory with 10 automated tests:

Test Script Description Points
basic_ls_al.sh Tests basic command execution 2
echo_hello_os_lab.sh Tests echo with quoted strings 2
redirect_create_output1.sh Tests output redirection creates file 2
cat_input_output1.sh Tests input redirection 2
append_and_grep_line.sh Tests append mode and piping 2
count_ls_wc.sh Tests pipe with ls and wc 2
background_sleep2.sh Tests background execution 2
cd_up_message.sh Tests cd command with messages 2
stderr_redirection.sh Tests stderr redirection (2>) 2
pwd_test.sh Tests pwd command 2
log_file_linecount.sh Tests log.txt has correct line count 2

Running Tests

Run All Tests

bash run_all_and_sum.sh

Output:

basic_ls_al.sh -> 2
echo_hello_os_lab.sh -> 2
redirect_create_output1.sh -> 2
...
TOTAL=22

Run Individual Test

cd testcases
bash basic_ls_al.sh

Test Dependencies

Some tests depend on previous tests:

  1. redirect_create_output1.sh creates output1.txt
  2. cat_input_output1.sh reads from output1.txt
  3. append_and_grep_line.sh appends to output1.txt

Run tests in order specified in run_all_and_sum.sh.


πŸ” Implementation Details

Memory Management

  • Dynamic Allocation: Uses malloc()/realloc() for arrays
  • Cleanup: Command::clear() frees all allocated memory
  • String Duplication: Uses strdup() for storing strings

File Descriptor Management

// Save defaults
int defaultin = dup(0);
int defaultout = dup(1);
int defaulterr = dup(2);

// ... perform redirections ...

// Restore
dup2(defaultin, 0);
dup2(defaultout, 1);
dup2(defaulterr, 2);
close(defaultin);
close(defaultout);
close(defaulterr);

Why? Prevents side effects between commands.

Pipe Implementation

int fdpipe[2];
pipe(fdpipe);           // Create pipe
// Child 1: dup2(fdpipe[1], 1);  // Write end β†’ stdout
// Child 2: dup2(fdpipe[0], 0);  // Read end β†’ stdin

Signal Handlers

// SIGINT: Prevent Ctrl-C from killing shell
sa_int.sa_handler = sigint_handler;
sa_int.sa_flags = SA_RESTART;
sigaction(SIGINT, &sa_int, nullptr);

// SIGCHLD: Log terminated background processes
sa_chld.sa_handler = sigchld_handler;
sa_chld.sa_flags = SA_RESTART | SA_NOCLDSTOP;
sigaction(SIGCHLD, &sa_chld, nullptr);

⚠️ Error Handling

Error Cases Handled

  1. Command Not Found: execvp() fails β†’ Print "execvp: No such file or directory"
  2. File Not Found: Input file doesn't exist β†’ Print "open input file: No such file or directory"
  3. Permission Denied: Can't open file β†’ Print "open output file: Permission denied"
  4. Invalid Pipe: Pipe before command β†’ Print "Error: Invalid pipe usage"
  5. Fork Failure: Can't create process β†’ Print "fork: Resource temporarily unavailable"
  6. Missing Redirection Filename: ls > with no filename β†’ Print "Error: Expected filename after redirection operator"

Example Error Output

myshell> cat nonexistent.txt
cat: nonexistent.txt: No such file or directory

myshell> ls > /root/file.txt
open output file: Permission denied

myshell> | grep test
Error: Invalid pipe usage

πŸ“š Additional Notes

Debugging

Uncomment print() in Command::execute() to see the parsed command table:

void Command::execute() {
    print();  // Uncomment this line
    // ...
}

Output:

              COMMAND TABLE
  #   Simple Commands
  --- ----------------------------------------------------------
  0   "ls"    "-al"

  Output       Input        Error        Err&Out       Background
  ------------ ------------ ------------ ------------ ------------
  output.txt   default      default      NO           NO

Limitations

  1. No command history: Up/down arrows don't work
  2. No tab completion: Can't auto-complete filenames
  3. No job control: Can't use fg, bg, jobs commands
  4. No aliases: Can't create command shortcuts
  5. No environment variable expansion: $HOME not supported (except in cd)

Future Enhancements

  • Add command history with readline library
  • Implement job control (fg, bg, jobs)
  • Support environment variables ($VAR)
  • Add command aliases
  • Implement command substitution ($(command))

πŸ‘¨β€πŸ’» Author & Credits

Course: CC373 - Operating Systems
Institution: Alexandria University, Faculty of Engineering
Program: Computer & Communication Engineering
Lab: Lab 3 - Mini Shell

Implementation Features:

  • βœ… Command parsing and execution
  • βœ… I/O redirection (>, >>, <, 2>, >>&)
  • βœ… Pipes (single and multiple)
  • βœ… Background processes (&)
  • βœ… Built-in commands (cd, exit)
  • βœ… Signal handling (SIGINT, SIGCHLD)
  • βœ… Process logging
  • βœ… Bonus: Wildcard expansion (*, ?)

πŸ“„ License

This project is for educational purposes as part of the Operating Systems course curriculum.


Happy Shelling! 🐚# Mini-Shell

Mini-Shell

Mini-Shell

About

A comprehensive Unix-like shell implementation in C++ featuring command parsing, I/O redirection, pipes, background processes, and wildcard expansion.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors