Eine Unix-Shell in C++ — gebaut um zu verstehen, was wirklich passiert wenn man Enter drückt.
- Powerline-Style — zeigt Verzeichnis, Git Branch, Git Status (
✓/✗) und Uhrzeit - Vollständig konfigurierbar — alle Farben über
~/.myshrcanpassbar - Startup Animation — Logo beim Start (instant, typewriter oder fade-in)
- Farbiges
ls— Dateien nach Typ eingefärbt (C++, JS/TS, HTML, CSS, JSON, Java, Python, ...) echo— mit Variablen-Support ($HOME,$PATH, ...)export— Umgebungsvariablen setzenalias— Abkürzungen definieren, werden in~/.myshrcgespeicherthistory— alle Befehle der Session anzeigenconfig— öffnet~/.myshrcdirekt in nvimsf [filter]— Datei fuzzy suchen und in$EDITORöffnensd [filter]— Ordner fuzzy suchen und direkt navigieren
- Pipes
|, Sequenz;, bedingte Ausführung&&/|| - I/O Umleitung
>,>>,< - Tilde-Expansion, Command History, Tab Autocomplete
- Quoted Arguments, Ctrl+C
Startup-Zeit, gemessen mit hyperfine auf Arch Linux (x86_64), 2000+ Läufe pro Shell:
| Shell | Startup |
|---|---|
| dash | 0.53 ms |
| bash | 0.85 ms |
| zsh | 0.92 ms |
| mysh | 1.5 ms |
Mit aktivem Git-Prompt innerhalb eines Repositories liegt mysh bei 4.0 ms.
Die Differenz ist der git status-Subprozess für die Clean/Dirty-Anzeige —
gemessen mit strace -c entfielen darauf rund zwei Drittel der gesamten
Syscall-Zeit. Ausserhalb eines interaktiven Terminals wird das
Prompt-Rendering deshalb komplett übersprungen.
Zur Einordnung: bash und zsh lesen bei nicht-interaktivem stdin ihre rc-Dateien nicht ein und zeigen in diesem Vergleich keinen Git-Status.
Reproduzieren:
echo exit > exit.txt
hyperfine -N --warmup 20 --input exit.txt ./shell /bin/dash /bin/bash /bin/zshDas -N ist wichtig — ohne wird jeder Lauf durch /bin/sh gestartet und
man misst überwiegend dessen Overhead.
Geprüft mit Valgrind (Memcheck) über einen Durchlauf mit Pipes, Redirections, Aliases und fehlschlagenden Kommandos:
ERROR SUMMARY: 0 errors from 0 contexts
definitely lost: 0 bytes in 0 blocks
indirectly lost: 0 bytes in 0 blocks
possibly lost: 0 bytes in 0 blocks
Die verbleibenden "still reachable"-Blöcke stammen aus readline, das seine internen Puffer beim Beenden nicht freigibt.
valgrind --leak-check=full --track-origins=yes ./shell < cmds.txt- C++17 oder neuer
- readline ≥ 8.0
- make
- fzf (für
sfundsd)
Arch Linux
sudo pacman -S readline fzf
git clone https://github.com/Mirac61/mysh
cd mysh && make && ./shellLinux (Debian/Ubuntu)
sudo apt install libreadline-dev fzf
git clone https://github.com/Mirac61/mysh
cd mysh && make && ./shellmacOS
brew install readline fzf
git clone https://github.com/Mirac61/mysh
cd mysh && make && ./shellcp examples/example.myshrc ~/.myshrcAlle Optionen
| Option | Werte | Beschreibung |
|---|---|---|
prompt_folder |
0–255 | Farbe des Ordnernamens |
prompt_time |
0–255 | Farbe der Uhrzeit |
prompt_git_clean |
0–255 | Farbe bei sauberem Repo |
prompt_git_dirty |
0–255 | Farbe bei uncommitted Änderungen |
startup |
0–3 | 0 = kein Logo, 1 = sofort, 2 = typewriter, 3 = fade-in |
alias |
name="befehl" |
Eigene Shortcuts |
Alle 256 Farben anzeigen:
for i in {0..255}; do echo -e "\033[48;5;${i}m $i \033[0m"; done./test.shBenutzung
# Navigation
cd ~/Projekte
ls -la
# Fuzzy Search
sf main # Datei suchen → in $EDITOR öffnen
sd Proj # Ordner suchen → direkt navigieren
# Pipes und Umleitung
ls | grep .cpp
ls > output.txt
# Bedingte Ausführung
make && ./shell
cd /nicht/vorhanden || echo "Verzeichnis nicht gefunden"
# Aliases und Variablen
alias ll="ls -la"
export NAME=Mirac
echo $NAMEProjektstruktur
mysh/
├── include/
│ └── shell.hpp # Deklarationen, Farb- und Konstanten-Definitionen
├── src/
│ ├── main.cpp # Hauptschleife, Prompt-Aufbau
│ ├── builtins.cpp # Built-in Befehle (cd, export, alias, sf, sd, ...)
│ ├── parse.cpp # Input-Parsing, Pipes, Redirect, Quote-Handling
│ ├── execute.cpp # Prozessausführung, Pipes, I/O Umleitung
│ ├── process.cpp # Job-Verwaltung, Hintergrundprozesse
│ ├── config.cpp # ~/.myshrc laden und speichern
│ ├── git.cpp # Git-Erkennung für Prompt
│ ├── ls.cpp # eigenes ls mit Dateityp-Färbung
│ └── startup.cpp # Startup-Animation
├── tests/
│ └── shell_tests.cpp
├── examples/
│ └── example.myshrc # Beispiel-Konfiguration
├── benchmark-results/
└── Makefile
Warum?
Die meisten Tutorials erklären die Theorie. Ich wollte eine Shell bauen und dabei wirklich verstehen, was zwischen Enter-drücken und Output-sehen passiert —
fork(), execvp(), dup2(), pipe(), alles davon.
Die Animationen wurden mit Unterstützung von Claude (Anthropic) entwickelt.
