rush-sh 0.1.1

A POSIX sh-compatible shell written in Rust
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
# Rush - A Unix shell written in Rust

<img src="images/rush_logo.png" alt="Rush Logo" width="50%" style="display: block; margin: 20px auto;">

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
  - `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

  ```bash
  MY_VAR=hello
  MY_VAR="hello world"
  NAME="Alice"
  ```

- **Variable Expansion**: Expand variables in commands with `$VAR` syntax

  ```bash
  echo "Hello $NAME"
  echo "Current directory: $PWD"
  ```

- **Special Variables**: Built-in support for special shell variables

  ```bash
  echo "Last exit code: $?"
  echo "Shell PID: $$"
  echo "Script name: $0"
  ```

- **Export Mechanism**: Export variables to child processes

  ```bash
  export MY_VAR
  export NEW_VAR=value
  ```

- **Variable Management**: Full lifecycle management with `unset`

  ```bash
  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:

```bash
# 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:

```bash
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:

```bash
# 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:

```bash
/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.

```bash
# 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:

```bash
# 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:**

```bash
# 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)

## Installation

### Prerequisites

- Rust (edition 2021 or later)

### Build

1. Clone the repository:

   ```bash
   git clone https://github.com/drewwalton19216801/rush.git
   cd rush
   ```

2. Build the project:
   ```bash
   cargo build --release
   ```

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

## Usage

### Interactive Mode

Run the shell without arguments to enter interactive mode:

```bash
./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:

```bash
./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:

```bash
./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:

```bash
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`
- 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: `cd``cd `, `e``echo `, `env `, `exit `
  - Complete files: `cat f``cat file.txt `
  - Complete directories: `cd src/``cd src/main/`
  - Complete from PATH: `l``ls `, `g``grep `
  - Complete nested paths: `ls src/m``ls 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:

```bash
cargo test
```

Run specific test modules:

```bash
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)
- 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](LICENSE.txt) for details.

## Project URL

[https://github.com/drewwalton19216801/rush](https://github.com/drewwalton19216801/rush)