Initial commit: psh - minimal bash-like shell with && || operators and tab completion
这个提交包含在:
@@ -0,0 +1,27 @@
|
|||||||
|
CC = gcc
|
||||||
|
CFLAGS = -Wall -Wextra -std=c99 -O2
|
||||||
|
LDFLAGS =
|
||||||
|
|
||||||
|
TARGET = psh.exe
|
||||||
|
SRC = psh.c
|
||||||
|
|
||||||
|
.PHONY: all clean test
|
||||||
|
|
||||||
|
all: $(TARGET)
|
||||||
|
|
||||||
|
$(TARGET): $(SRC)
|
||||||
|
$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
|
||||||
|
|
||||||
|
test: $(TARGET)
|
||||||
|
@echo "Testing psh..."
|
||||||
|
./$(TARGET) -c "echo hello"
|
||||||
|
./$(TARGET) -c "pwd"
|
||||||
|
./$(TARGET) -c "dir | findstr .exe"
|
||||||
|
@echo "Tests passed!"
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f $(TARGET) *.o
|
||||||
|
|
||||||
|
# Build with readline support (if available)
|
||||||
|
readline: $(SRC)
|
||||||
|
$(CC) $(CFLAGS) -DHAVE_READLINE -lreadline -o $(TARGET) $(SRC)
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
# psh - A minimal bash-like shell in pure C
|
||||||
|
|
||||||
|
A lightweight POSIX shell implementation featuring:
|
||||||
|
|
||||||
|
- Command execution via fork/exec (Windows: CreateProcess)
|
||||||
|
- Built-in commands: `cd`, `pwd`, `echo`, `exit`, `help`, `env`
|
||||||
|
- Input/output redirection (`<`, `>`, `>>`)
|
||||||
|
- Piping (`|`)
|
||||||
|
- Background execution (`&`)
|
||||||
|
- Command history with up/down arrows
|
||||||
|
- Tab completion
|
||||||
|
- `&&` and `||` operators
|
||||||
|
- Basic environment variable support
|
||||||
|
- Tilde expansion (`~`)
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
Requires:
|
||||||
|
- GCC or compatible C compiler
|
||||||
|
- GNU Readline library (for line editing)
|
||||||
|
|
||||||
|
## Running
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./psh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### Built-ins
|
||||||
|
- `cd [dir]` - Change directory
|
||||||
|
- `pwd` - Print working directory
|
||||||
|
- `echo [args]` - Print text (supports `$VAR` expansion)
|
||||||
|
- `exit` - Exit shell
|
||||||
|
- `help` - Show help
|
||||||
|
- `env [var=val]` - Show or set environment variables
|
||||||
|
- `history` - Show command history
|
||||||
|
|
||||||
|
### Redirection
|
||||||
|
```bash
|
||||||
|
ls > output.txt # Redirect output
|
||||||
|
cat < input.txt # Redirect input
|
||||||
|
echo hi >> log.txt # Append output
|
||||||
|
```
|
||||||
|
|
||||||
|
### Piping
|
||||||
|
```bash
|
||||||
|
ps aux | grep python | wc -l
|
||||||
|
```
|
||||||
|
|
||||||
|
### Background
|
||||||
|
```bash
|
||||||
|
sleep 100 &
|
||||||
|
```
|
||||||
|
|
||||||
|
### Logical Operators
|
||||||
|
```bash
|
||||||
|
cmd1 && cmd2 # Execute cmd2 only if cmd1 succeeds
|
||||||
|
cmd1 || cmd2 # Execute cmd2 only if cmd1 fails
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tab Completion
|
||||||
|
Press `Tab` to complete commands:
|
||||||
|
- Built-in commands (cd, pwd, echo, etc.)
|
||||||
|
- Executable files in PATH and current directory
|
||||||
|
- Environment variables
|
||||||
|
|
||||||
|
### Command History
|
||||||
|
- ↑/↓ arrows: Navigate history
|
||||||
|
- Tab: Try completion
|
||||||
|
- Backspace: Delete character
|
||||||
|
|
||||||
|
## Example Session
|
||||||
|
|
||||||
|
```
|
||||||
|
user@localhost:F:\Paze SH> echo Hello World
|
||||||
|
Hello World
|
||||||
|
|
||||||
|
user@localhost:F:\Paze SH> ls && echo "List completed"
|
||||||
|
psh.c README.md Makefile
|
||||||
|
List completed
|
||||||
|
|
||||||
|
user@localhost:F:\Paze SH> false || echo "Fallback executed"
|
||||||
|
Fallback executed
|
||||||
|
|
||||||
|
user@localhost:F:\Paze SH> help
|
||||||
|
psh - minimal bash-like shell (Windows)
|
||||||
|
|
||||||
|
Built-in commands:
|
||||||
|
cd [dir] Change directory
|
||||||
|
pwd Print working directory
|
||||||
|
echo [args] Print arguments
|
||||||
|
exit Exit shell
|
||||||
|
env [var=val] Show/set environment
|
||||||
|
help Show this help
|
||||||
|
|
||||||
|
Features:
|
||||||
|
cmd1 | cmd2 Pipe output
|
||||||
|
cmd > file Redirect output
|
||||||
|
cmd >> file Append output
|
||||||
|
cmd < file Redirect input
|
||||||
|
cmd & Run in background
|
||||||
|
cmd1 && cmd2 Execute cmd2 if cmd1 succeeds
|
||||||
|
cmd1 || cmd2 Execute cmd2 if cmd1 fails
|
||||||
|
history View command history
|
||||||
|
Tab Try command completion
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* psh - Test cases for the shell
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
/* Test helper */
|
||||||
|
int run_test(const char *cmd, const char *expected_substring) {
|
||||||
|
char output[4096];
|
||||||
|
FILE *fp;
|
||||||
|
int passed = 0;
|
||||||
|
|
||||||
|
printf("Testing: %s\n", cmd);
|
||||||
|
|
||||||
|
/* Run command and capture output */
|
||||||
|
char full_cmd[4096];
|
||||||
|
snprintf(full_cmd, sizeof(full_cmd), "psh.exe -c \"%s\" 2>&1", cmd);
|
||||||
|
|
||||||
|
fp = _popen(full_cmd, "r");
|
||||||
|
if (fp == NULL) {
|
||||||
|
printf(" FAILED: Could not execute command\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(output, 0, sizeof(output));
|
||||||
|
if (fread(output, 1, sizeof(output) - 1, fp) > 0) {
|
||||||
|
/* Remove trailing newline */
|
||||||
|
output[strcspn(output, "\n")] = '\0';
|
||||||
|
|
||||||
|
if (strstr(output, expected_substring) != NULL) {
|
||||||
|
printf(" PASSED: Got expected output containing '%s'\n", expected_substring);
|
||||||
|
passed = 1;
|
||||||
|
} else {
|
||||||
|
printf(" FAILED: Expected output containing '%s', got: '%s'\n", expected_substring, output);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
printf(" FAILED: No output from command\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
_pclose(fp);
|
||||||
|
return passed;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
int passed = 0;
|
||||||
|
int total = 0;
|
||||||
|
|
||||||
|
printf("=== psh Test Suite ===\n\n");
|
||||||
|
|
||||||
|
/* Test 1: echo */
|
||||||
|
total++;
|
||||||
|
if (run_test("echo hello", "hello")) passed++;
|
||||||
|
|
||||||
|
/* Test 2: pwd */
|
||||||
|
total++;
|
||||||
|
if (run_test("pwd", "F:\\Paze SH")) passed++;
|
||||||
|
|
||||||
|
/* Test 3: environment variables */
|
||||||
|
total++;
|
||||||
|
if (run_test("echo $HOME", "")) passed++; /* Just check it doesn't crash */
|
||||||
|
|
||||||
|
/* Test 4: help */
|
||||||
|
total++;
|
||||||
|
if (run_test("help", "Built-in commands")) passed++;
|
||||||
|
|
||||||
|
/* Test 5: history (empty) */
|
||||||
|
total++;
|
||||||
|
if (run_test("history", "history")) passed++;
|
||||||
|
|
||||||
|
/* Test 6: && operator */
|
||||||
|
total++;
|
||||||
|
if (run_test("echo first && echo second", "first")) passed++;
|
||||||
|
|
||||||
|
/* Test 7: || operator */
|
||||||
|
total++;
|
||||||
|
if (run_test("false || echo fallback", "fallback")) passed++;
|
||||||
|
|
||||||
|
printf("\n=== Results: %d/%d tests passed ===\n", passed, total);
|
||||||
|
|
||||||
|
return (passed == total) ? 0 : 1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
test1: echo Hello World
|
||||||
|
test2: pwd
|
||||||
|
test3: ls | findstr .exe
|
||||||
|
test4: echo $HOME
|
||||||
|
test5: mkdir testdir && cd testdir && pwd
|
||||||
|
test6: false || echo "This should print"
|
||||||
|
test7: true && echo "This should also print"
|
||||||
|
test8: env
|
||||||
在新工单中引用
屏蔽一个用户