rush-sh 0.2.1

A POSIX sh-compatible shell written in Rust
rush-sh-0.2.1 is not a library.
Visit the last successful build: rush-sh-0.8.0

Rush - A Unix shell written in Rust

Rush is a POSIX sh-compatible shell implemented in Rust. It provides both interactive mode with a REPL prompt and script mode for executing commands from files. The shell supports basic shell features like command execution, pipes, redirections, environment variables, and built-in commands.

Pun-der the Hood

  • In a hurry? Don’t bash your head against it—Rush it.
  • When your pipelines need a drum solo, put them on Rush and let the commands Neil-Peart through.
  • Tom Sawyer tip: chores go faster when you make them look like a Rush job; no need to paint the fence by hand when the shell can whitewash it with a one-liner.
  • Alias your productivity: alias hurry='rush -c "do the thing"'—because sometimes you just need to rush to judgment.
  • This shell doesn’t just run fast; it gives you the Rush of a clean exit status.

Features

  • Command Execution: Execute external commands and built-in commands.
  • Pipes: Chain commands using the | operator.
  • Redirections: Input (<) and output (>, >>) redirections.
  • Command Substitution: Execute commands and substitute their output inline.
    • $(command) syntax: echo "Current dir: $(pwd)"
    • `command` syntax: echo "Files: ls | wc -l"
    • Variable expansion within substitutions: echo $(echo $HOME)
    • Error handling with fallback to literal syntax
  • Environment Variables: Full support for variable assignment, expansion, and export.
    • Variable assignment: VAR=value and VAR="quoted value"
    • Variable expansion: $VAR and special variables ($?, $$, $0)
    • Export mechanism: export VAR and export VAR=value
    • Variable scoping: Shell variables vs exported environment variables
  • Control Structures:
    • if statements: if condition; then commands; elif condition; then commands; else commands; fi
    • case statements with glob pattern matching: case word in pattern1|pattern2) commands ;; *.txt) commands ;; *) default ;; esac
  • Built-in Commands:
    • cd: Change directory
    • exit: Exit the shell
    • echo: Print text
    • pwd: Print working directory
    • env: List environment variables
    • export: Export variables to child processes
    • unset: Remove variables
    • source: Execute a script file with rush (bypasses shebang)
    • pushd: Push directory onto stack and change to it
    • popd: Pop directory from stack and change to it
    • dirs: Display directory stack
    • alias: Define or display aliases
    • unalias: Remove alias definitions
    • test / [: POSIX-compatible test builtin with string and file tests
    • help: Show available commands
  • Tab Completion: Intelligent completion for commands, files, and directories.
    • Command Completion: Built-in commands and executables from PATH
    • File/Directory Completion: Files and directories with relative paths
    • Directory Traversal: Support for nested paths (src/, ../, /usr/bin/)
    • Home Directory Expansion: Completion for ~/ and ~/Documents/ paths
  • Signal Handling: Graceful handling of SIGINT (Ctrl+C) and SIGTERM.
  • Line Editing and History: Enhanced interactive experience with rustyline.

Latest Updates

Environment Variable Support

Rush now provides comprehensive environment variable support with full POSIX compliance:

  • Variable Assignment: Support for both simple and quoted assignments

    MY_VAR=hello
    MY_VAR="hello world"
    NAME="Alice"
    
  • Variable Expansion: Expand variables in commands with $VAR syntax

    echo "Hello $NAME"
    echo "Current directory: $PWD"
    
  • Special Variables: Built-in support for special shell variables

    echo "Last exit code: $?"
    echo "Shell PID: $$"
    echo "Script name: $0"
    
  • Export Mechanism: Export variables to child processes

    export MY_VAR
    export NEW_VAR=value
    
  • Variable Management: Full lifecycle management with unset

    unset MY_VAR
    
  • Multi-Mode Support: Variables work consistently across all execution modes

    • Interactive mode: Variables persist across commands
    • Script mode: Variables available throughout script execution
    • Command string mode: Variables work in -c command strings

Example usage:

# Set and use variables
MY_VAR="Hello from Rush"
echo "Message: $MY_VAR"

# Export to child processes
export MY_VAR
env | grep MY_VAR

# Use in pipelines
echo "$MY_VAR" | grep "Rush"

# Special variables
if true; then echo "Success ($?)"; fi

Case Statements with Glob Pattern Matching

Rush now supports advanced case statements with full glob pattern matching capabilities:

  • Glob Patterns: Use wildcards like * (any characters), ? (single character), and [abc] (character classes)
  • Multiple Patterns: Combine patterns with | (e.g., *.txt|*.md)
  • POSIX Compliance: Full support for standard case statement syntax
  • Performance: Efficient pattern matching using the glob crate

Example usage:

case $filename in
    *.txt|*.md) echo "Text file" ;;
    *.jpg|*.png) echo "Image file" ;;
    file?) echo "Single character file" ;;
    [abc]*) echo "Starts with a, b, or c" ;;
    *) echo "Other file type" ;;
esac

Directory Stack Support (pushd/popd/dirs)

Rush now supports directory stack management with the classic Unix pushd, popd, and dirs commands:

  • pushd <directory>: Changes to the specified directory and pushes the current directory onto the stack
  • popd: Pops the top directory from the stack and changes to it
  • dirs: Displays the current directory stack

Example usage:

# Start in home directory
pwd
# /home/user

# Push to /tmp and see stack
pushd /tmp
# /tmp /home/user

# Push to another directory
pushd /var
# /var /tmp /home/user

# See current stack
dirs
# /var /tmp /home/user

# Pop back to /tmp
popd
# /tmp /home/user

# Pop back to home
popd
# /home/user

This feature is particularly useful for:

  • Quickly switching between multiple working directories
  • Maintaining context when working on different parts of a project
  • Scripting scenarios that require directory navigation

Command Substitution

Rush now supports comprehensive command substitution with both $(...) and `...` syntax:

  • Dual Syntax Support: Both $(command) and `command` work identically
  • Immediate Execution: Commands are executed during lexing and output is substituted inline
  • Variable Expansion: Variables within substituted commands are properly expanded
  • Error Handling: Failed commands fall back to literal syntax preservation
  • Environment Integration: Child processes inherit shell environment variables
  • Multi-line Support: Handles commands with multiple lines and special characters

Example usage:

Condensed Current Working Directory in Prompt

Rush now displays a condensed version of the current working directory in the interactive prompt:

  • Condensed Path: Each directory except the last is abbreviated to its first letter (preserving case)
  • Full Last Directory: The final directory in the path is shown in full
  • Dynamic Updates: The prompt updates automatically when changing directories

Example prompt displays:

/h/d/p/r/rush $
/u/b/s/project $
/h/u/Documents $

This feature provides context about your current location while keeping the prompt concise.

# Basic command substitution
echo "Current directory: $(pwd)"
echo "Files in directory: `ls | wc -l`"

# Variable assignments with substitutions
PROJECT_DIR="$(pwd)/src"
FILE_COUNT="$(ls *.rs 2>/dev/null | wc -l)"

# Complex expressions
echo "Rust version: $(rustc --version | cut -d' ' -f2)"
echo "Files modified today: $(find . -name '*.rs' -mtime -1 | wc -l)"

# Error handling
NONEXISTENT="$(nonexistent_command 2>/dev/null || echo 'command failed')"
echo "Result: $NONEXISTENT"

# Multiple commands
echo "Combined output: $(echo 'Hello'; echo 'World')"

Command substitution works seamlessly with:

  • Pipes and Redirections: $(echo hello | grep ll) > output.txt
  • Variable Expansion: echo $(echo $HOME)
  • Quoted Strings: echo "Path: $(pwd)"
  • Complex Commands: $(find . -name "*.rs" -exec wc -l {} \;)

Built-in Alias Support

Rush now provides comprehensive built-in alias support, allowing you to create shortcuts for frequently used commands:

  • Create Aliases: Define shortcuts with alias name=value syntax
  • List Aliases: View all defined aliases with alias command
  • Show Specific Alias: Display a single alias with alias name
  • Remove Aliases: Delete aliases with unalias name
  • Automatic Expansion: Aliases are expanded automatically during command execution
  • Recursion Prevention: Built-in protection against infinite alias loops

Example usage:

# Create aliases
alias ll='ls -l'
alias la='ls -la'
alias ..='cd ..'
alias grep='grep --color=auto'

# List all aliases
alias
# Output:
# alias ll='ls -l'
# alias la='ls -la'
# alias ..='cd ..'
# alias grep='grep --color=auto'

# Show specific alias
alias ll
# Output: alias ll='ls -l'

# Use aliases (they expand automatically)
ll
la /tmp
..

# Remove aliases
unalias ll
alias  # ll is no longer listed

# Error handling
unalias nonexistent  # Shows: unalias: nonexistent: not found

Key Features:

  • POSIX Compliance: Follows standard alias syntax and behavior
  • Session Persistence: Aliases persist throughout the shell session
  • Complex Commands: Support for multi-word commands and pipelines
  • Variable Expansion: Variables in aliases are expanded when defined
  • Safety: Automatic detection and prevention of recursive aliases

Advanced Usage:

# Complex aliases with pipes and redirections
alias backup='cp -r ~/Documents ~/Documents.backup && echo "Backup completed"'
alias count='find . -name "*.rs" | wc -l'

# Aliases with variables (expanded at definition time)
MY_DIR="/tmp"
alias cleanup="rm -rf $MY_DIR/*"

# Function-like aliases
alias mkcd='mkdir -p "$1" && cd "$1"'  # Note: $1 won't work as expected

Implementation Details:

  • Aliases are expanded after lexing but before parsing
  • Only the first word of a command can be an alias
  • Expansion is recursive (aliases can reference other aliases)
  • Built-in protection against infinite recursion
  • Aliases work in all execution modes (interactive, script, command)

Test Builtin with Conditional Logic

Rush now provides comprehensive support for the POSIX test builtin command and its [ bracket syntax, enabling powerful conditional logic in shell scripts:

  • String Tests: Check if strings are empty (-z) or non-empty (-n)
  • File Tests: Test file existence (-e), regular files (-f), and directories (-d)
  • Dual Syntax Support: Both test and [ syntax work identically
  • POSIX Compliance: Full compatibility with standard test command behavior
  • Error Handling: Proper exit codes (0=true, 1=false, 2=error)
  • Integration: Seamless integration with shell control structures

Example usage:

# String tests
if test -z ""; then echo "Empty string"; fi
if [ -n "hello" ]; then echo "Non-empty string"; fi

# File tests
if test -e "/tmp/file.txt"; then echo "File exists"; fi
if [ -d "/tmp" ]; then echo "Is directory"; fi
if test -f "/etc/passwd"; then echo "Is regular file"; fi

# Complex conditions
if [ -n "$MY_VAR" ] && test -e "$CONFIG_FILE"; then
    echo "Variable set and config file exists"
fi

# Error handling
test -x "invalid_option"  # Returns exit code 2
exit_code=$?
if [ $exit_code -eq 2 ]; then echo "Invalid option used"; fi

Key Features:

  • String Operations: -z (zero length) and -n (non-zero length) tests
  • File Operations: -e (exists), -f (regular file), -d (directory)
  • Bracket Syntax: [ condition ] works identically to test condition
  • Exit Codes: 0 (true), 1 (false), 2 (error/invalid usage)
  • Variable Expansion: Variables are properly expanded in test conditions
  • Nested Conditions: Works with complex if/elif/else structures

Advanced Usage:

# Variable existence checks
MY_VAR="hello world"
if test -n "$MY_VAR"; then
    echo "MY_VAR is set to: $MY_VAR"
fi

# Safe file operations
TARGET_FILE="/tmp/safe_file.txt"
if test -e "$TARGET_FILE"; then
    echo "File exists, backing up..."
    mv "$TARGET_FILE" "$TARGET_FILE.backup"
fi

# Directory creation with checks
TARGET_DIR="/tmp/test_dir"
if test -d "$TARGET_DIR"; then
    echo "Directory already exists"
else
    mkdir -p "$TARGET_DIR"
    echo "Directory created"
fi

The test builtin is fully integrated with Rush's control structures, enabling complex conditional logic in scripts while maintaining POSIX compatibility.

Installation

Prerequisites

  • Rust (edition 2021 or later)

Build

  1. Clone the repository:

    git clone https://github.com/drewwalton19216801/rush.git
    cd rush
    
  2. Build the project:

    cargo build --release
    

The binary will be available at target/release/rush.

Usage

Interactive Mode

Run the shell without arguments to enter interactive mode:

./target/release/rush

You'll see a prompt showing the condensed current working directory followed by $ (e.g., /h/d/p/r/rush $ ) where you can type commands. Type exit to quit.

Script Mode

Execute commands from a file:

./target/release/rush script.sh

The shell will read and execute each line from the script file. Note that when using script mode, shebang lines (e.g., #!/usr/bin/env bash) are not bypassed - they are executed as regular comments.

Command Mode

Execute a command string directly:

./target/release/rush -c "echo Hello World"

The shell will execute the provided command string and exit.

Source Command

The source built-in command provides a way to execute script files while bypassing shebang lines that may specify other shells:

source script.sh

This is particularly useful for:

  • Executing scripts written for rush that contain #!/usr/bin/env rush shebangs
  • Running scripts with shebangs for other shells (like #!/usr/bin/env bash) using rush instead
  • Ensuring consistent execution environment regardless of shebang declarations

Unlike script mode (running ./target/release/rush script.sh), the source command automatically skips shebang lines and executes all commands using the rush interpreter.

Examples

  • Run a command: ls -la
  • Use pipes: ls | grep txt
  • Redirect output: echo "Hello" > hello.txt
  • Change directory: cd /tmp
  • Print working directory: pwd
  • Directory stack management:
    • Push directory: pushd /tmp
    • Pop directory: popd
    • Show stack: dirs
  • Execute a script: source script.sh
  • Execute a script with shebang bypass: source examples/basic_commands.sh
  • Execute elif example script: source examples/elif_example.sh
  • Execute case example script: source examples/case_example.sh
  • Execute variables example script: source examples/variables_example.sh
  • Execute complex example script with command substitution: source examples/complex_example.sh
  • Alias management:
    • Create aliases: alias ll='ls -l'; alias la='ls -la'
    • List aliases: alias
    • Show specific alias: alias ll
    • Remove aliases: unalias ll
    • Use aliases: ll /tmp
  • Environment variables:
    • Set variables: MY_VAR=hello; echo $MY_VAR
    • Export variables: export MY_VAR=value; env | grep MY_VAR
    • Special variables: echo "Exit code: $?"; echo "PID: $$"
    • Quoted values: NAME="John Doe"; echo "Hello $NAME"
  • Use control structures:
    • If statement: if true; then echo yes; else echo no; fi
    • If-elif-else statement: if false; then echo no; elif true; then echo yes; else echo maybe; fi
    • Case statement with glob patterns:
      • Simple match: case hello in hello) echo match ;; *) echo no match ;; esac
      • Glob patterns: case file.txt in *.txt) echo "Text file" ;; *.jpg) echo "Image" ;; *) echo "Other" ;; esac
      • Multiple patterns: case file in *.txt|*.md) echo "Document" ;; *.exe|*.bin) echo "Executable" ;; *) echo "Other" ;; esac
      • Character classes: case letter in [abc]) echo "A, B, or C" ;; *) echo "Other letter" ;; esac
  • Test builtin for conditional logic:
    • String tests: if test -z "$VAR"; then echo "Variable is empty"; fi
    • File tests: if [ -f "/etc/passwd" ]; then echo "File exists"; fi
    • Combined conditions: if test -n "$NAME" && [ -d "/tmp" ]; then echo "Ready"; fi
    • Error handling: test -x "invalid"; echo "Exit code: $?"
  • Command substitution:
    • Basic substitution: echo "Current dir: $(pwd)"
    • Backtick syntax: echo "Files: ls | wc -l"
    • Variable assignments: PROJECT_DIR="$(pwd)/src"
    • Complex commands: echo "Rust version: $(rustc --version | cut -d' ' -f2)"
    • Error handling: RESULT="$(nonexistent_command 2>/dev/null || echo 'failed')"
    • With pipes: $(echo hello | grep ll) > output.txt
    • Multiple commands: echo "Output: $(echo 'First'; echo 'Second')"
  • Tab completion:
    • Complete commands: cdcd , eecho , env , exit
    • Complete files: cat fcat file.txt
    • Complete directories: cd src/cd src/main/
    • Complete from PATH: lls , ggrep
    • Complete nested paths: ls src/mls src/main/

Architecture

Rush consists of the following components:

  • Lexer: Tokenizes input into commands, operators, and variables with support for variable expansion, command substitution ($(...) and `...` syntax), and alias expansion.
  • Parser: Builds an Abstract Syntax Tree (AST) from tokens, including support for complex control structures, case statements with glob patterns, and variable assignments.
  • Executor: Executes the AST, handling pipes, redirections, built-ins, glob pattern matching, environment variable inheritance, and command substitution execution.
  • Shell State: Comprehensive state management for environment variables, exported variables, special variables ($?, $$, $0), current directory, directory stack, and command aliases.
  • Built-in Commands: Optimized detection and execution of built-in commands including variable management (export, unset, env) and alias management (alias, unalias).
  • Completion: Provides intelligent tab-completion for commands, files, and directories.

Dependencies

  • rustyline: For interactive line editing and history.
  • signal-hook: For robust signal handling.
  • nix: For Unix system interactions.
  • libc: For low-level C library bindings.
  • glob: For pattern matching in case statements.

Testing

Rush includes a comprehensive test suite to ensure reliability and correctness. The tests cover unit testing for individual components, integration testing for end-to-end functionality, and error handling scenarios.

Test Structure

  • Lexer Tests Tokenization of commands, arguments, operators, quotes, variable expansion, command substitution, and edge cases.
  • Parser Tests AST construction for single commands, pipelines, redirections, if-elif-else statements, case statements with glob patterns, and error cases.
  • Executor Tests Built-in commands, external command execution, pipelines, redirections, case statement execution with glob matching, command substitution execution, and error handling.
  • Completion Tests Tab-completion for commands, files, directories, path traversal, and edge cases.
  • Integration Tests End-to-end command execution, including pipelines, redirections, variable expansion, case statements, and command substitution.
  • Main Tests Error handling for invalid directory changes.

Running Tests

Run all tests with:

cargo test

Run specific test modules:

cargo test lexer
cargo test parser
cargo test executor
cargo test integration

Test Coverage

The test suite provides extensive coverage of:

  • Command parsing and execution
  • Built-in command functionality (cd, echo, pwd, env, exit, help, source, export, unset, pushd, popd, dirs, alias, unalias, test, [)
  • Pipeline and redirection handling
  • Control structures (if-elif-else statements, case statements with glob patterns)
  • Command substitution ($(...) and `...` syntax, error handling, variable expansion)
  • Environment variable support (assignment, expansion, export, special variables)
  • Variable scoping and inheritance
  • Tab-completion for commands, files, and directories
  • Path traversal and directory completion
  • Error conditions and edge cases
  • Signal handling integration

Contributing

Contributions are welcome! Please fork the repository, make your changes, and submit a pull request.

License

This project is licensed under the MIT License. See LICENSE.txt for details.

Project URL

https://github.com/drewwalton19216801/rush-sh