Skip to content
AarambhaAntaPublic

About

A lightweight, modern C++ Unix shell implementation focused on simplicity and educational value.

Topics

Resources

Contributing

Stars

5 stars

Watchers

1 watching

Forks

Latest commit

 

History

70 Commits

Folders and files

Repository files navigation

ninxsh - A Custom Unix Shell

LICENSE Last Commit Language Stars

ninxsh is a lightweight shell written in modern C++. It supports:

  • Enhanced Terminal Prompt: Modern username@hostname:path$ format with ANSI colors
  • Smart Prompt Display: Colored prompt for interactive use, plain format when piped
  • Builtin Commands (exit, cd, clear, history, jobs, kill, fg, bg)
  • External executable support using fork() and execvp()
  • Input/output redirection (<, >)
  • Command pipelines (|) with multiple commands
  • Background process execution (&)
  • Job control and management (jobs, kill <pid>, fg [job_id], bg [job_id])
  • Signal handling (Ctrl+C, Ctrl+Z)
  • Path expansion (~ to home directory)
  • Environment variable expansion ($HOME, $USER, etc.)
  • Advanced quote handling (single quotes, double quotes, escape sequences)
  • Zombie process cleanup with automatic job status updates
  • DoS protection with configurable limits (centralized in limits.hpp)
  • Comprehensive test suite for all features
  • Command history with persistent storage and execution (!!, !n)

Build Instructions

Universal Build (Cross-Platform)

make         # Build the shell in default mode
make debug   # Build with debug symbols and without optimizations
make release # Build with optimizations
make sanitize # Build with sanitizers for catching memory issues
make format  # Format code using clang-format
make test    # Run the test suite
make install # Install the shell (requires sudo)
make clean   # Delete build artifacts
make help    # Show available commands

Platform-Optimized Builds

For platform-specific optimizations and features, use the specialized Makefiles:

# macOS (Xcode toolchain, code signing, app bundles)
cd Resources/Mac && make

# Linux (distribution detection, package creation, valgrind)
cd Resources/Linux && make

# Windows (MinGW/MSYS2, installers, portable builds)
cd Resources/Windows && make

Platform-Specific Features

macOS (Resources/Mac/):

  • Debug symbols for lldb (make dsym)
  • Code signing (make codesign)
  • App bundle creation (make bundle)
  • Apple Silicon support (make install-arm64)

Linux (Resources/Linux/):

  • Auto-detect distribution and package manager
  • Memory testing (make valgrind)
  • Performance profiling (make perf)
  • Package creation (make deb, make rpm)
  • Static builds (make static)

Windows (Resources/Windows/):

  • NSIS installer creation (make installer)
  • Portable ZIP packages (make portable)
  • Static linking for portability
  • MSYS2 dependency management (make deps)

Developer Setup

To enable IDE features like auto-complete and jump-to-definition, generate a compile_commands.json:

make clean
bear -- make

Project Structure

ninxsh/
├── src/
│   ├── main.cpp        # Main loop
│   ├── shell.cpp       # Shell class logic
│   ├── command.cpp     # Tokenization
│   ├── executor.cpp    # External execution logic
│   ├── builtin.cpp     # Built-in command handlers
│   ├── utils.cpp       # Misc utilities
│   ├── history.cpp     # Command history
│   └── jobs.cpp        # Job management
├── include/
│   ├── shell.hpp
│   ├── command.hpp
│   ├── executor.hpp
│   ├── builtin.hpp
│   ├── utils.hpp
│   ├── history.hpp
│   ├── jobs.hpp
│   └── limits.hpp      # DoS protection constants
├── tests/
│   ├── test_runner.cpp         # Test runner framework
│   ├── test_command.cpp        # Command parsing tests
│   ├── test_builtin.cpp        # Builtin command tests
│   ├── test_utils.cpp          # Utility function tests
│   ├── test_executor.cpp       # Command execution tests
│   ├── test_io_pipeline.cpp    # I/O and pipeline tests
│   ├── test_signal.cpp         # Signal handling tests
│   ├── test_history.cpp        # Command history tests
│   ├── test_dos_protection.cpp # DoS protection tests
│   └── test_jobs.cpp           # Job management tests
├── Resources/
│   ├── Mac/Makefile            # macOS-optimized build
│   ├── Linux/Makefile          # Linux-optimized build
│   └── Windows/Makefile        # Windows-optimized build
├── Makefile                    # Universal build system
├── README.md                   # Project documentation
└── .gitignore                  # Git ignore file

To generate the initial project structure:

mkdir -p ninxsh/{src,include,tests,Resources/{Mac,Linux,Windows}} \
&& touch ninxsh/src/{main.cpp,shell.cpp,command.cpp,executor.cpp,builtin.cpp,utils.cpp,history.cpp,jobs.cpp} \
&& touch ninxsh/include/{shell.hpp,command.hpp,executor.hpp,builtin.hpp,utils.hpp,history.hpp,jobs.hpp,limits.hpp} \
&& touch ninxsh/tests/{test_runner.cpp,test_command.cpp,test_builtin.cpp,test_utils.cpp,test_executor.cpp,test_io_pipeline.cpp,test_signal.cpp,test_history.cpp,test_dos_protection.cpp,test_jobs.cpp} \
&& touch ninxsh/Resources/{Mac,Linux,Windows}/Makefile \
&& touch ninxsh/{Makefile,README.md,.gitignore}

Progress Roadmap

Day 1 - Shell Initialization

  • Project compiles with make
  • Print Prompt: ninxsh >
  • Reads user input
  • Gracefully handles empty input and Ctrl + C (EOF)
  • Initial commits in Git

Day 2 - External Command Execution

  • Run external commands using fork() and execvp()
  • Supports basic Unix commands like ls, pwd, echo, date, etc.
  • Basic error handling for invalid commands (shows "command not found")
  • Robust input tokenization and conversion to C-style argument arrays
  • Handles command execution filure gracefully without crashing
  • Proper memory management with strdup() and free()

Day 3 - Builtin Commands

  • Implemented exit
  • Implemented cd [path] (fallback $HOME)
  • Implemented clear using ANSI escape sequence
  • Implemented history with persistent storage and execution (!!, !n)
  • Implemented jobs for listing background jobs
  • Implemented kill <pid> for terminating processes
  • Implemented fg [job_id] for bringing jobs to foreground
  • Implemented bg [job_id] for resuming jobs in background
  • Builtins are handled in parent process (no fork)

Day 4 - Path resolution

  • Handle absolute & relative path commands
  • Support for ~ expansion to $HOME
  • Support for environment variable expansion like $HOME, $USER

Day 5 - I/O Redirection

  • Input Redirection (command < file.txt)
  • Output Redirection (command > file.txt)
  • Used dup2() for redirection
  • Refactored execution into executor.cpp

Day 6 - Command Pipelines

  • Support for piping commands (ls -l | grep txt)
  • Implemented multi-command pipeline execution with pipe() and multiple fork()s
  • Refactored Command and ParsedCommand to support pipelines
  • Added separate executePipeline() function for handling piped commands

Day 7 - Background Execution

  • Support for background processes with & (sleep 10 &)
  • Print job PID when running in background
  • Implemented SIGCHLD handler to avoid zombie processes
  • Used non-blocking waitpid() with WNOHANG for cleanup
  • Added proper signal handling with sigaction()

Day 8 - Robust Signal Handling & Testing

  • Implemented comprehensive signal handling (SIGINT, SIGTSTP, SIGCHLD)
  • Shell doesn't terminate on Ctrl+C or Ctrl+Z
  • Ensured proper foreground/background state management
  • Added zombie process cleanup system
  • Created comprehensive test suite with test runner
  • Added unit tests for commands, builtins, utils, and executor
  • Implemented integration tests for I/O redirection and pipelines
  • Added signal handling tests
  • Enhanced Makefile with debug/release/sanitize/format targets
  • Added test infrastructure with proper pattern rules

Day 9 - Command History

  • Implemented history command to view command history
  • Store previous commands in memory
  • Save history to ~/.ninxsh_history file
  • Load history from file when shell starts
  • Automatic cleanup of oldest commands when history size exceeds limit
  • Support for !! to execute the last command
  • Support for !n to execute command number n from history

Day 10 - Security & Performance Improvements

  • Added explicit error state handling to ParsedCommand structure
  • Implemented comprehensive DoS protection with input length validation
  • Prevented history pollution by validating commands before storage
  • Centralized all protection limits in include/limits.hpp header
  • Optimized regex compilation with static const patterns
  • Standardized test file naming to test_*.cpp convention
  • Added boundary condition tests and enhanced error reporting
  • Updated documentation with security features and architecture

Day 11 - Terminal Improvements

  • Enhanced prompt format: Modern username@hostname:path$ replacing emoji prompt
  • ANSI colored prompt for interactive terminal sessions
    • Username in bold green, hostname in blue, path in yellow
    • Smart home directory replacement with ~
    • Intelligent path truncation for very long directories
  • Smart terminal detection with isOutputToTerminal()
  • Plain text prompt when output is piped to prevent formatting issues
  • Cross-platform compatibility with robust fallbacks
  • Added complete ANSI Colors namespace (16 colors + formatting)
  • Comprehensive terminal utility functions (getCurrentUsername, getCurrentHostname, getCurrentWorkingDir)
  • Enhanced test coverage for all terminal functionality
  • Updated documentation with new terminal features

Day 12 - Advanced Quote Handling

  • Comprehensive quote parsing with new tokenizer in src/command.cpp
  • Proper single quote handling (literal strings, no variable expansion)
  • Proper double quote handling (allows variable expansion)
  • Escape sequence support with backslash (\) for special characters
  • Mixed quoting support (single, double, and unquoted in same command)
  • Quote-aware environment variable expansion (respects quote context)
  • Pipeline support with quoted arguments
  • Comprehensive test suite in tests/test_quotes.cpp with 7 test cases
  • Fixed expandPath() to only handle tilde (~) expansion, not environment variables
  • All tests passing (10/10) including quote handling integration

Security Features

DoS Protection

  • Input Length Validation: Commands longer than 4KB are rejected
  • Path Length Limits: File paths longer than 2KB are handled gracefully
  • String Processing Limits: Environment variable expansion limited to 2KB strings
  • Centralized Configuration: All limits defined in include/limits.hpp
  • Explicit Error Handling: Clear error messages for rejected input
  • History Protection: Invalid commands are not stored in command history

Performance Optimizations

  • Static Regex Compilation: Environment variable patterns compiled once
  • Early Exit Strategies: Skip expensive operations when possible
  • Memory Management: Proper cleanup with RAII patterns

Testing

The ninxsh project includes a comprehensive C++ test suite covering all major functionality.

Running All Tests

make test    # Run complete test suite

The test suite includes:

  • Command parsing - Input tokenization and validation
  • Builtin commands - All builtin functions (cd, exit, clear, history, jobs, kill, fg, bg)
  • External execution - Fork/exec functionality and process management
  • I/O redirection & pipelines - File redirection and command chaining
  • Signal handling - Ctrl+C, Ctrl+Z, and process cleanup
  • Job management - Background job tracking and control
  • History functionality - Command storage and expansion
  • DoS protection - Input validation and security limits
  • Utility functions - Helper and utility code

All tests use a unified framework and report PASSED/FAILED status.

Manual Testing Examples

# Test normal operation
echo "hello world"
cd ~
ls | grep txt

# Test history
history
!!
!1

# Test environment variables
echo $HOME
cd $HOME

# Test background jobs and job control
sleep 30 &           # Start background job
jobs                 # List active jobs
kill 1234            # Kill process by PID
fg 1                 # Bring job 1 to foreground
bg 1                 # Resume job 1 in background

# Test pipelines with background
ls -la | grep txt &  # Run pipeline in background
jobs                 # View the background pipeline

Contributing

Contributions are welcome! Please read CONTRIBUTING.md for guidelines on how to contribute to this project.

Development Workflow

  1. Fork the repository
  2. Clone your fork locally
  3. Create a feature branch (git checkout -b feature/amazing-feature)
  4. Make your changes with proper tests
  5. Test thoroughly (make test)
  6. Format code (make format)
  7. Commit with descriptive messages
  8. Push to your branch (git push origin feature/amazing-feature)
  9. Open a Pull Request

License

This project is licensed under the MIT License - see the LICENSE file for details.

Contributors

Special thanks to the following people who have contributed to making ninxsh better:

  • @piotrmaciejbednarski - Discovered and reported critical DoS vulnerability in input processing, provided detailed reproduction steps and suggested security improvements that led to the implementation of comprehensive DoS protection in Day 10

Want to see your name here? Check out our Contributing Guidelines and submit a pull request!

Acknowledgments

  • Built with modern C++17 standards
  • Inspired by traditional Unix shells (bash, zsh, tcsh)
  • Cross-platform compatibility with macOS, Linux, and Windows
  • Comprehensive testing and documentation practices

Contact & Support


ninxsh - A modern Unix shell written in C++

⭐ Star this repo if you find it useful! ⭐

Built with ❤️ by developers, for developers

About

A lightweight, modern C++ Unix shell implementation focused on simplicity and educational value.

Topics

Resources

Contributing

Stars

5 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages