Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
511 lines
28 KiB
Markdown
511 lines
28 KiB
Markdown
`gosh` eine Shell, in golang geschrieben.
|
||
|
||
```
|
||
gosh - Go Shell
|
||
Version: 0.13.0 (branch: main, commit: f79fb0b)
|
||
|
||
Built-in commands:
|
||
cd [dir] - Change directory (cd with no args goes to home)
|
||
pwd - Print working directory
|
||
echo [args] - Echo arguments
|
||
exit [code] - Exit shell with status code (default: $?)
|
||
quit [code] - Quit shell with status code (default: $?)
|
||
set VAR=val - Set environment variable
|
||
export VAR=val - Export environment variable
|
||
unset VAR - Remove a variable
|
||
source file - Run the commands of file in this shell (also: . file)
|
||
jobs - List background and stopped jobs
|
||
fg [%n] - Continue job n (default: current job) in the foreground
|
||
bg [%n] - Continue stopped job n in the background
|
||
wait [%n] - Wait for job n or all background jobs
|
||
alias [name[=value] ...] - Define or show aliases (alias ll='ls -l')
|
||
unalias name - Remove an alias (unalias -a: all aliases)
|
||
: - Do nothing (exit status 0)
|
||
break [n] - Leave the innermost (or n-th) for/while/until loop
|
||
continue [n] - Continue with the next iteration of a loop
|
||
help - Show this help
|
||
|
||
Features:
|
||
- Argument parsing with quotes and escapes ('...' is literal,
|
||
"..." expands variables, \ escapes the next character)
|
||
- Ctrl+D to exit, Ctrl+C to cancel the current line or command
|
||
- Command history (up/down arrows), stored in
|
||
$XDG_STATE_HOME/gosh/history (default ~/.local/state/gosh/history,
|
||
override with $GOSH_HISTFILE); lines starting with a space are not saved
|
||
- Tab completion for commands and file paths
|
||
- Tilde expansion (~, ~/path) at the start of unquoted words
|
||
- Brace expansion: a{b,c}d -> abd acd, {1..5}, {01..10..2}, {a..e}
|
||
- Wildcards: * ? [abc] [a-z] [!abc] match file names (not hidden files
|
||
unless the pattern starts with a dot); quoted wildcards and values of
|
||
variables are not expanded, a pattern without match is kept as is
|
||
- Prompt user@host:dir $, with [N] before the $ if the last command
|
||
failed with exit status N
|
||
- Colored prompt (user@host green, red for root; directory blue; exit
|
||
status red), configurable with $GOSH_COLORS, e.g.
|
||
GOSH_COLORS='user=1;33:path=36' (keys user, root, path, error; values
|
||
are ANSI SGR codes, empty = no color); disabled with $NO_COLOR,
|
||
TERM=dumb or when output is not a terminal
|
||
- Variables: name=value sets a variable (like set), name=value command
|
||
sets it only for that command; the value is not split or expanded as
|
||
a pattern
|
||
- Environment variable expansion ($VAR, ${VAR}, ${VAR:-default});
|
||
using an unset variable is an error, use ${VAR:-} for an empty default
|
||
- Command substitution: $(command) is replaced by the output of command
|
||
(run in a subshell); unquoted it is split into words, "$(command)"
|
||
stays one word
|
||
- Command lists: a ; b (run both), a && b (b only if a succeeded),
|
||
a || b (b only if a failed); $? is the exit status of the last command
|
||
- Control structures: if cmd; then ...; elif cmd; then ...; else ...; fi,
|
||
for x in words; do ...; done, while cmd; do ...; done, until ...,
|
||
! cmd negates the status; they can span several lines (prompt "> ")
|
||
- Pipes (a | b) and redirections: < file, > file, >> file, 2> file,
|
||
2>> file, 2>&1, >&2, &> file (stdout and stderr), &>> file
|
||
- Job control: command & runs in the background, Ctrl+Z stops the
|
||
foreground job; jobs, fg, bg and wait manage the jobs
|
||
- Aliases replace the first word of a command in interactive shells
|
||
(define them in ~/.goshrc); \name or 'name' skips the alias
|
||
- Non-interactive use: gosh -c 'command', gosh script, command | gosh;
|
||
the exit status is the one of the last command
|
||
- Startup files: login shells (-l or argv[0] starting with -) read
|
||
/etc/gosh/profile and ~/.gosh_profile, interactive shells read
|
||
/etc/gosh/goshrc and ~/.goshrc (skip with --noprofile, --norc)
|
||
|
||
```
|
||
|
||
### Aufruf
|
||
|
||
```
|
||
gosh [-l] [-i] [--norc] [--noprofile] [script]
|
||
gosh [-l] [--noprofile] -c command
|
||
gosh -version
|
||
```
|
||
|
||
| Aufruf | Verhalten |
|
||
|------------------------|------------------------------------------------------------------|
|
||
| `gosh` | interaktive Shell (wenn stdin ein Terminal ist) |
|
||
| `gosh -c 'befehl'` | führt den Befehl aus und beendet sich |
|
||
| `gosh skript.gosh` | führt das Skript Zeile für Zeile aus |
|
||
| `befehle \| gosh` | liest die Befehle von stdin |
|
||
| `-i` | erzwingt den interaktiven Modus, auch ohne Terminal |
|
||
| `-l`, `--login` | Login-Shell, liest die Profil-Dateien (siehe unten) |
|
||
| `--norc` | `goshrc`-Dateien nicht lesen |
|
||
| `--noprofile` | `profile`-Dateien nicht lesen |
|
||
|
||
Ohne Terminal bricht gosh bei einem fehlgeschlagenen Befehl nicht ab und beendet sich mit dem Exit-Status des letzten Befehls: `127`, wenn ein Befehl nicht gefunden wurde, `126`, wenn er nicht ausführbar ist, `128+n`, wenn er durch Signal `n` beendet wurde. Leere Zeilen und Zeilen, die mit `#` beginnen, werden übersprungen, Skripte können also mit `#!/usr/bin/gosh` beginnen.
|
||
|
||
Damit funktionieren auch `ssh host befehl`, `scp` und `rsync`, wenn gosh als Login-Shell gesetzt ist. Befehle lassen sich mit `;`, `&&` und `||` verketten und mit Pipes verbinden, z. B. `ssh host 'cd /srv && git log | head'`.
|
||
|
||
### Startdateien
|
||
|
||
Beim Start liest gosh Startdateien und führt sie wie ein Skript in der laufenden Shell aus (Variablen, die dort gesetzt werden, bleiben also erhalten):
|
||
|
||
| Shell | Dateien (in dieser Reihenfolge) | abschalten mit |
|
||
|-------------------------------|----------------------------------------------|-----------------|
|
||
| Login-Shell | `/etc/gosh/profile`, `~/.gosh_profile` | `--noprofile` |
|
||
| interaktive Shell | `/etc/gosh/goshrc`, `~/.goshrc` | `--norc` |
|
||
|
||
- Eine Login-Shell ist gosh mit `-l` oder wenn `argv[0]` mit `-` beginnt (so starten `login`, `sshd` und `su -` die Shell). Eine interaktive Login-Shell liest erst die Profil-, dann die `goshrc`-Dateien.
|
||
- `gosh -c '…'` und Skripte lesen keine Startdateien, außer als Login-Shell (`gosh -l -c '…'`).
|
||
- Fehlende Dateien werden übersprungen. Fehler werden mit Datei und Zeile gemeldet (`gosh: ~/.goshrc:4: foo: command not found`), die übrigen Zeilen laufen trotzdem.
|
||
- Bash-Dateien wie `/etc/profile` oder `~/.bashrc` liest gosh bewusst nicht, weil es deren Syntax (`if`, Funktionen, …) nicht versteht.
|
||
- Mit `source datei` (oder `. datei`) lässt sich eine Datei auch später einlesen, z. B. nach einer Änderung: `source ~/.goshrc`.
|
||
|
||
Beispiel für `~/.goshrc`:
|
||
|
||
```bash
|
||
# Eigene Programme zuerst
|
||
export PATH=$HOME/bin:$HOME/go/bin:$PATH
|
||
export EDITOR=vim
|
||
|
||
# Aliase
|
||
alias ll='ls -l'
|
||
alias ..='cd ..'
|
||
|
||
# Prompt-Farben (Anführungszeichen wegen des ;)
|
||
export GOSH_COLORS='user=1;38;5;208:path=38;5;205'
|
||
|
||
# Weitere Einstellungen nur, wenn die Datei existiert
|
||
test -f ~/.goshrc.local && source ~/.goshrc.local
|
||
```
|
||
|
||
Beispiel für `~/.gosh_profile` (wird nur von Login-Shells gelesen, z. B. beim Anmelden an der Konsole oder per `ssh host` ohne Befehl):
|
||
|
||
```bash
|
||
export LANG=de_DE.UTF-8
|
||
export PATH=$HOME/bin:$PATH
|
||
```
|
||
|
||
### Kontrollstrukturen
|
||
|
||
gosh kennt `if`, `for`, `while` und `until` wie `sh`, auf einer Zeile oder über mehrere Zeilen:
|
||
|
||
```bash
|
||
if [ -f ~/.goshrc ]; then
|
||
echo "rc vorhanden"
|
||
elif [ -d ~/.config ]; then
|
||
echo "nur .config"
|
||
else
|
||
echo "nichts"
|
||
fi
|
||
|
||
for f in *.log; do
|
||
gzip "$f"
|
||
done
|
||
|
||
for i in {1..5}; do echo "Durchlauf $i"; done
|
||
|
||
n=3
|
||
while [ $n -gt 0 ]; do
|
||
echo $n
|
||
n=$(expr $n - 1)
|
||
done
|
||
|
||
until ping -c1 -W1 server >/dev/null; do sleep 1; done
|
||
```
|
||
|
||
| Konstrukt | Bedeutung |
|
||
|---------------------------------------------|-------------------------------------------------------------------|
|
||
| `if befehl; then …; elif …; then …; else …; fi` | führt den Zweig aus, dessen Bedingung den Exit-Status 0 hat |
|
||
| `for x in wörter; do …; done` | setzt `x` nacheinander auf jedes Wort |
|
||
| `while befehl; do …; done` | wiederholt, solange `befehl` erfolgreich ist |
|
||
| `until befehl; do …; done` | wiederholt, bis `befehl` erfolgreich ist |
|
||
| `! befehl` | kehrt den Exit-Status um |
|
||
| `break [n]`, `continue [n]` | verlässt die (n-te) Schleife bzw. springt zum nächsten Durchlauf |
|
||
| `:` | tut nichts, Exit-Status 0 (z. B. `while :; do …; done`) |
|
||
|
||
- Bedingungen sind normale Befehle (auch Listen mit `&&`/`||`), meist `test` bzw. `[ … ]` (z. B. `[ -f datei ]`, `[ "$a" = b ]`, `[ $n -lt 3 ]`).
|
||
- Die Wörter nach `for … in` werden wie Argumente expandiert: `*.go`, `{1..10}`, `$VAR`, `$(befehl)`. Ohne `in` läuft die Schleife nicht (Positionsparameter gibt es nicht).
|
||
- Variablen, `$(...)` und Wildcards im Rumpf werden bei jedem Durchlauf neu ersetzt.
|
||
- Ist ein Befehl noch nicht vollständig (z. B. nach `if …; then` oder `for …` + Enter, nach `&&`, `|` oder einem `\` am Zeilenende), zeigt gosh den Fortsetzungs-Prompt `> `; Ctrl+C verwirft die angefangene Eingabe. Skripte und Startdateien lesen entsprechend weiter.
|
||
- Ctrl+C bricht die ganze Befehlszeile ab, also auch eine Schleife (`while true; do sleep 1; done`).
|
||
- Der Exit-Status ist der des zuletzt ausgeführten Befehls; ein `if` ohne passenden Zweig und eine Schleife ohne Durchlauf haben den Status 0.
|
||
- Nicht unterstützt: Kontrollstrukturen in Pipes (`for …; done | sort`), mit Umleitung (`done > datei`) oder im Hintergrund (`done &`), außerdem `case`, Funktionen und `read`. Arithmetik geht z. B. mit `n=$(expr $n + 1)`.
|
||
|
||
### Variablen
|
||
|
||
```bash
|
||
name=wert # Variable setzen (wie set name=wert)
|
||
a=1 b=2 # mehrere auf einmal
|
||
gruss="Hallo Welt" # Leerzeichen im Wert brauchen Anführungszeichen
|
||
heute=$(date +%F) # Ausgabe eines Befehls
|
||
PATH=~/bin:$PATH # ~ wird auch nach = und : ersetzt
|
||
LANG=C sort datei # nur für diesen einen Befehl
|
||
unset name # Variable entfernen
|
||
echo $name ${name:-standard}
|
||
```
|
||
|
||
- Eine Zuweisung ist ein Wort `name=wert` am Anfang eines Befehls, bei dem `name` und `=` ohne Anführungszeichen stehen und `name` aus Buchstaben, Ziffern und `_` besteht (nicht mit einer Ziffer beginnend). `"x=1"`, `x\=1` und `echo x=1` sind keine Zuweisungen.
|
||
- Steht danach kein Befehl, wird die Variable in der Shell gesetzt. Wie bei `set` und `export` sind in gosh alle Variablen auch für gestartete Programme sichtbar (es gibt keine reinen Shell-Variablen).
|
||
- Steht ein Befehl danach (`LANG=C sort datei`), gilt die Zuweisung nur für diesen Befehl, die Shell behält den alten Wert.
|
||
- Der Wert wird nicht in Wörter geteilt und nicht als Wildcard oder `{a,b}` expandiert: `x=$(ls)` speichert die ganze Ausgabe, `x=*.go` speichert `*.go`. Variablen, `$(...)` und `~` (am Anfang, nach `=` und nach `:`) werden ersetzt.
|
||
- `$?` ist nach einer Zuweisung der Status des letzten `$(...)` darin, sonst 0.
|
||
- Variablen in den Argumenten werden ersetzt, bevor die Zuweisung davor gilt: `x=1 echo $x` gibt nicht `1` aus, sondern meldet `x: unbound variable` (wenn `x` vorher nicht gesetzt war).
|
||
- In einer Pipeline (`x=1 | cat`) ändert eine Zuweisung die Shell nicht.
|
||
|
||
### Befehlsersetzung
|
||
|
||
`$(befehl)` wird durch die Ausgabe von `befehl` ersetzt:
|
||
|
||
```
|
||
$ echo "Heute ist $(date +%A)"
|
||
Heute ist Montag
|
||
$ kill $(pgrep -f mein-server)
|
||
$ cd $(git rev-parse --show-toplevel)
|
||
$ vim $(grep -l TODO *.go)
|
||
$ echo "Dateien: $(ls | wc -l)"
|
||
```
|
||
|
||
- Der Befehl läuft in einer eigenen Subshell (einem neuen gosh-Prozess): `cd`, `set` oder `export` darin ändern die laufende Shell nicht. Er darf alles enthalten, was gosh kann (Pipes, `;`, `&&`, Umleitungen, verschachtelte `$(...)`), und liest von der gleichen Eingabe, Fehlermeldungen gehen auf stderr.
|
||
- Abschließende Zeilenumbrüche der Ausgabe werden entfernt.
|
||
- Ohne Anführungszeichen wird die Ausgabe an Leerzeichen, Tabs und Zeilenumbrüchen in mehrere Wörter geteilt, `"$(befehl)"` bleibt ein einziges Wort (mit den Zeilenumbrüchen darin).
|
||
- Anders als in `sh` werden Wildcards in der Ausgabe nicht expandiert, und Zeichen wie `;` oder `|` darin sind normaler Text: Die Ausgabe ist immer nur Daten.
|
||
- Besteht ein Befehl nur aus einer Ersetzung (`$(false)`), ist ihr Exit-Status `$?`, sonst zählt der Status des Befehls. Ctrl+C während der Ersetzung bricht die ganze Zeile ab.
|
||
- In `'...'` oder mit `\$(` wird nichts ersetzt. Die alte Schreibweise mit Backticks (`` `befehl` ``) und Arithmetik `$((1+2))` gibt es nicht.
|
||
|
||
### Befehlslisten und Exit-Status
|
||
|
||
Mehrere Befehle in einer Zeile werden wie in `sh` verknüpft:
|
||
|
||
| Liste | Bedeutung |
|
||
|------------|---------------------------------------------------|
|
||
| `a ; b` | erst `a`, dann `b` |
|
||
| `a && b` | `b` nur, wenn `a` erfolgreich war (Exit-Status 0) |
|
||
| `a \|\| b` | `b` nur, wenn `a` fehlgeschlagen ist |
|
||
|
||
`&&` und `||` werden von links nach rechts ausgewertet, `a && b || c` führt also `c` aus, wenn `a` oder `b` fehlschlägt. Variablen werden erst beim Ausführen des jeweiligen Befehls expandiert, `cd /tmp && echo $PWD` gibt also schon das neue Verzeichnis aus.
|
||
|
||
`$?` (bzw. `${?}`) enthält den Exit-Status des letzten Befehls, `exit` ohne Argument beendet gosh mit diesem Status:
|
||
|
||
```
|
||
$ false; echo $?
|
||
1
|
||
$ gibtsnicht || echo "Status $?"
|
||
gosh: gibtsnicht: command not found
|
||
Status 127
|
||
```
|
||
|
||
Schlägt ein Programm fehl, gibt gosh selbst keine Meldung mehr aus (das Programm meldet den Fehler selbst), nur bei Fehlern von gosh, z. B. wenn ein Befehl nicht gefunden wird.
|
||
|
||
### Klammer-Expansion
|
||
|
||
Wie in bash erzeugt `{…}` aus einem Wort mehrere Wörter:
|
||
|
||
| Ausdruck | ergibt |
|
||
|-------------------|---------------------------------|
|
||
| `a{b,c}d` | `abd acd` |
|
||
| `file{,.bak}` | `file file.bak` |
|
||
| `{a,b}{1,2}` | `a1 a2 b1 b2` |
|
||
| `x{a,{b,c}}` | `xa xb xc` (verschachtelt) |
|
||
| `{1..5}` | `1 2 3 4 5` |
|
||
| `{5..1}` | `5 4 3 2 1` |
|
||
| `{1..10..3}` | `1 4 7 10` (Schrittweite) |
|
||
| `{01..10..4}` | `01 05 09` (mit führenden Nullen) |
|
||
| `{a..e}` | `a b c d e` |
|
||
|
||
```
|
||
$ mkdir -p projekt/{src,doc,test}
|
||
$ cp config.yaml{,.bak} # Sicherungskopie
|
||
$ ls *.{go,md}
|
||
$ touch log-{01..12}.txt
|
||
```
|
||
|
||
- Die Klammer-Expansion kommt zuerst, die Ergebnisse durchlaufen danach `~`, Variablen und Wildcards: `*.{go,md}` wird zu `*.go *.md`, `{${A},b}` zu `$A b`.
|
||
- Ohne Komma (und ohne gültige Folge) bleiben Klammern stehen: `{a}`, `{}`, `{a..1}`. Auch `${VAR}` wird nicht angefasst.
|
||
- In Anführungszeichen oder mit `\` davor sind die Zeichen normale Zeichen: `"{a,b}"`, `\{a,b\}`. Ein Komma lässt sich mit `\,` schützen: `{a\,b,c}` ergibt `a,b c`.
|
||
- Bei einer Umleitung muss genau ein Wort herauskommen, `> {a,b}` meldet `ambiguous redirect`.
|
||
- Ein Wort darf höchstens 100 000 Wörter erzeugen.
|
||
|
||
### Wildcards
|
||
|
||
Wörter mit `*`, `?` oder `[...]` werden durch die passenden Dateinamen ersetzt, sortiert:
|
||
|
||
| Muster | passt auf |
|
||
|-----------|-----------------------------------------------------|
|
||
| `*` | beliebig viele Zeichen (auch keine) |
|
||
| `?` | genau ein Zeichen |
|
||
| `[abc]` | eines der Zeichen `a`, `b`, `c` |
|
||
| `[a-z]` | ein Zeichen aus dem Bereich |
|
||
| `[!abc]` | ein Zeichen außer `a`, `b`, `c` (auch `[^abc]`) |
|
||
|
||
```
|
||
$ ls *.go
|
||
main.go main_test.go
|
||
$ cat logs/2026-*/error.log
|
||
$ ls -d */ # nur Verzeichnisse
|
||
```
|
||
|
||
- Muster gelten pro Pfadteil, `*` passt nie auf `/`: `*/*.go` findet `.go`-Dateien eine Ebene tiefer.
|
||
- Versteckte Dateien (mit `.` am Anfang) werden nur gefunden, wenn das Muster selbst mit `.` beginnt: `*` findet `.bashrc` nicht, `.*rc` schon.
|
||
- Passt keine Datei, bleibt das Wort unverändert stehen (wie in `sh`), `ls *.xyz` meldet dann also `ls` den Fehler.
|
||
- In Anführungszeichen oder mit `\` davor sind die Zeichen normale Zeichen: `echo "*"`, `echo '*.go'`, `echo \*`.
|
||
- Werte von Variablen werden nicht als Muster behandelt (anders als in `sh`): Nach `P='*.go'` gibt `echo $P` wörtlich `*.go` aus. So kann ein Variablenwert nie unbemerkt auf viele Dateien passen. `echo $DIR/*.go` funktioniert dagegen, weil das `*` direkt in der Zeile steht.
|
||
- Bei einer Umleitung muss das Muster genau eine Datei treffen (`> *.log`), sonst meldet gosh `ambiguous redirect`.
|
||
|
||
### Pipes und Umleitungen
|
||
|
||
```
|
||
$ ls | sort -r | head -3
|
||
$ make > build.log 2>&1
|
||
$ grep -c error < build.log
|
||
```
|
||
|
||
| Syntax | Bedeutung |
|
||
|-----------------|------------------------------------------------------------|
|
||
| `a \| b` | stdout von `a` wird stdin von `b` |
|
||
| `< datei` | stdin aus `datei` lesen |
|
||
| `> datei` | stdout in `datei` schreiben (wird überschrieben) |
|
||
| `>> datei` | stdout an `datei` anhängen |
|
||
| `2> datei` | stderr in `datei` schreiben (`2>>` hängt an) |
|
||
| `2>&1` | stderr dorthin, wohin stdout gerade geht |
|
||
| `>&2` | stdout dorthin, wohin stderr gerade geht |
|
||
| `&> datei` | stdout und stderr in `datei` (`&>>` hängt an) |
|
||
| `> datei` | ohne Befehl: legt `datei` an bzw. leert sie |
|
||
|
||
- Umleitungen werden von links nach rechts ausgewertet: `befehl > log 2>&1` schreibt beides in `log`, `befehl 2>&1 > log` schickt stderr dorthin, wo stdout vorher hinging (z. B. in eine Pipe).
|
||
- Unterstützt werden die Dateideskriptoren 0, 1 und 2. Vor `>` und `<` zählt nur eine Zahl ohne Anführungszeichen als Deskriptor (`2>f`, aber nicht `x2>f` oder `"2">f`).
|
||
- Operatoren in Anführungszeichen oder mit `\` davor sind normale Zeichen: `echo "a|b" 'c>d' e\>f`.
|
||
- Der Exit-Status einer Pipeline ist der des letzten Befehls. Beendet sich ein Befehl am Ende (z. B. `head`), bekommt der schreibende Befehl davor `SIGPIPE`, `yes | head -1` endet also sofort.
|
||
- Die Built-ins `pwd` und `help` lassen sich umleiten und in Pipes verwenden (`help | less`). `cd`, `set`, `export`, `exit` und `quit` verändern die Shell selbst und sind in einer Pipeline nicht erlaubt.
|
||
|
||
### Aliase
|
||
|
||
Ein Alias ist ein Kurzname für einen Befehl, am besten in `~/.goshrc` definiert:
|
||
|
||
```bash
|
||
alias ll='ls -l'
|
||
alias la='ls -la'
|
||
alias ..='cd ..'
|
||
alias gs='git status'
|
||
alias ls='ls --color=auto' # ein Alias darf den eigenen Namen enthalten
|
||
alias please='sudo ' # endet mit Leerzeichen: das nächste Wort wird auch ersetzt
|
||
```
|
||
|
||
| Befehl | Bedeutung |
|
||
|-------------------------|---------------------------------------------------|
|
||
| `alias name='wert'` | definiert einen Alias (mehrere in einem Aufruf möglich) |
|
||
| `alias` | listet alle Aliase (in einer Form, die sich wieder einlesen lässt) |
|
||
| `alias name` | zeigt einen Alias |
|
||
| `unalias name` | entfernt einen Alias, `unalias -a` alle |
|
||
|
||
- Ersetzt wird nur das erste Wort eines Befehls, also am Zeilenanfang und nach `;`, `&&`, `||`, `|` und `&`: `ll | less` funktioniert, `echo ll` gibt `ll` aus.
|
||
- Der Wert darf selbst mehrere Befehle enthalten (`alias up='cd .. && ls'`) und wird wiederum auf Aliase geprüft. Ein Alias, der gerade ersetzt wird, wird darin nicht noch einmal ersetzt, deshalb funktioniert `alias ls='ls --color=auto'`.
|
||
- Endet der Wert mit einem Leerzeichen, wird auch das nächste Wort als Alias geprüft: Mit den Aliasen oben wird `please ll` zu `sudo ls --color=auto -l`.
|
||
- Mit `\ll` oder `'ll'` umgeht man den Alias und ruft das Programm direkt auf.
|
||
- Aliase gelten nur in interaktiven Shells (wie in bash), also auch in `~/.goshrc`, aber nicht in Skripten und bei `gosh -c`. Ein Alias wirkt erst ab der nächsten Zeile, nicht in der Zeile, in der er definiert wird.
|
||
- Erlaubt sind Namen aus Buchstaben, Ziffern und `_ - . + : , @ % ^`.
|
||
- Aliase werden auch mit Tab vervollständigt.
|
||
|
||
### Job-Control
|
||
|
||
In einer interaktiven Shell läuft jede Befehlszeile (bzw. jede Pipeline) als eigener *Job* in einer eigenen Prozessgruppe:
|
||
|
||
```
|
||
$ sleep 100 & # im Hintergrund starten
|
||
[1] 12345
|
||
$ vim notizen.txt # Ctrl+Z hält vim an
|
||
^Z
|
||
[2]+ Stopped vim notizen.txt
|
||
$ jobs
|
||
[1]- Running sleep 100 &
|
||
[2]+ Stopped vim notizen.txt
|
||
$ fg # vim wieder im Vordergrund
|
||
$ bg %2 # oder: angehaltenen Job im Hintergrund weiterlaufen lassen
|
||
```
|
||
|
||
| Befehl / Taste | Bedeutung |
|
||
|----------------|---------------------------------------------------------------------------|
|
||
| `befehl &` | startet den Befehl im Hintergrund, gosh wartet nicht auf ihn (`$?` ist 0) |
|
||
| Ctrl+Z | hält den Vordergrund-Job an (`$?` wird 128+SIGTSTP, unter Linux 148) |
|
||
| Ctrl+C | bricht nur den Vordergrund-Job ab, nicht die Shell |
|
||
| `jobs` | listet laufende und angehaltene Jobs |
|
||
| `fg [%n]` | holt Job `n` in den Vordergrund (und setzt ihn fort, falls angehalten) |
|
||
| `bg [%n]` | setzt einen angehaltenen Job im Hintergrund fort |
|
||
| `wait [%n]` | wartet auf Job `n` bzw. auf alle Hintergrund-Jobs |
|
||
|
||
- Ohne Angabe gilt der *aktuelle* Job (in `jobs` mit `+` markiert, der zuletzt angehaltene oder in den Hintergrund geschickte), `%-` ist der vorherige (`-`). `%n` und `n` meinen Job Nummer `n`.
|
||
- Beendete Hintergrund-Jobs meldet gosh vor dem nächsten Prompt (`[1]+ Done sleep 100`, bei Fehlern `Exit N`).
|
||
- Liest ein Hintergrund-Job vom Terminal, wird er angehalten (wie in bash), mit `fg` bekommt er die Eingabe.
|
||
- Die Terminal-Einstellungen werden gesichert und wiederhergestellt: Nach dem Anhalten von `vim` oder `less` funktioniert der Prompt normal, nach `fg` bekommt das Programm seine Einstellungen zurück.
|
||
- `exit` (oder Ctrl+D) mit angehaltenen Jobs warnt einmal (`there are stopped jobs`), ein zweites `exit` direkt danach beendet gosh.
|
||
- `cd`, `set`, `export`, `exit`, `source`, `fg`, `bg` und `wait` lassen sich nicht mit `&` im Hintergrund starten.
|
||
- In Skripten und mit `gosh -c` gibt es keine Job-Control (kein Ctrl+Z, kein `fg`/`bg`), `&` und `wait` funktionieren aber. Hintergrund-Jobs lesen dort von `/dev/null`.
|
||
- Wird ein angehaltener Job von außen fortgesetzt (`kill -CONT`), zeigt `jobs` ihn weiter als `Stopped`, bis er endet oder erneut angehalten wird; `fg` und `bg` funktionieren trotzdem.
|
||
- Einen Job beenden lässt sich mit `kill` über die Prozess-ID (z. B. `kill 12345`); `kill %1` versteht gosh noch nicht.
|
||
|
||
### Prompt
|
||
|
||
Der Prompt zeigt Benutzer, Rechner und Verzeichnis. Ist der letzte Befehl fehlgeschlagen, steht sein Exit-Status in eckigen Klammern davor, bis wieder ein Befehl erfolgreich war:
|
||
|
||
```
|
||
flow@host:~/src $ false
|
||
flow@host:~/src [1] $ gibtsnicht
|
||
gosh: gibtsnicht: command not found
|
||
flow@host:~/src [127] $ sleep 100 # mit Ctrl+C abgebrochen
|
||
^C
|
||
flow@host:~/src [130] $ true
|
||
flow@host:~/src $
|
||
```
|
||
|
||
Typische Werte: `1` allgemeiner Fehler, `2` Syntaxfehler, `126` nicht ausführbar, `127` Befehl nicht gefunden, `128+n` durch Signal `n` beendet (`130` = Ctrl+C, `148` = mit Ctrl+Z angehalten unter Linux). Eine leere Eingabe ändert den Status nicht.
|
||
|
||
### Farben
|
||
|
||
Der Prompt wird über die Umgebungsvariable `GOSH_COLORS` eingefärbt. Sie besteht aus durch `:` getrennten Einträgen `schlüssel=code`:
|
||
|
||
| Schlüssel | Teil des Prompts | Standard |
|
||
|-----------|--------------------------------|---------------------|
|
||
| `user` | `user@host` | `1;32` (fett grün) |
|
||
| `root` | `user@host`, wenn man root ist | `1;31` (fett rot) |
|
||
| `path` | aktuelles Verzeichnis | `1;34` (fett blau) |
|
||
| `error` | Exit-Status `[N]` nach einem Fehler | `1;31` (fett rot) |
|
||
|
||
Die Werte sind [ANSI-SGR-Codes](https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters), mehrere werden mit `;` kombiniert:
|
||
|
||
- `1` fett, `2` blass, `3` kursiv, `4` unterstrichen
|
||
- `30`–`37` Vordergrund, `90`–`97` helle Vordergrundfarben, `40`–`47` Hintergrund
|
||
- `38;5;N` eine der 256 Farben, `38;2;R;G;B` Truecolor (sofern das Terminal es kann)
|
||
|
||
Nicht angegebene Schlüssel behalten ihre Standardfarbe, ein leerer Wert (`path=`) schaltet die Farbe für diesen Teil ab. Ungültige Werte werden ignoriert. Da nur Ziffern und `;` erlaubt sind, lassen sich über die Variable keine beliebigen Escape-Sequenzen ins Terminal schreiben. `NO_COLOR` schaltet alle Farben ab.
|
||
|
||
Gesetzt wird die Variable z. B. in `~/.profile` oder direkt in gosh (wirkt ab dem nächsten Prompt):
|
||
|
||
```bash
|
||
export GOSH_COLORS='user=1;33:path=1;36' # in ~/.profile
|
||
set 'GOSH_COLORS=user=1;33:path=1;36' # in gosh
|
||
```
|
||
|
||
Ein paar Kombinationen zum Ausprobieren:
|
||
|
||
```bash
|
||
# Solarized: gelb und cyan
|
||
GOSH_COLORS='user=33:path=36'
|
||
|
||
# Sonnenuntergang: orange und pink (256 Farben)
|
||
GOSH_COLORS='user=1;38;5;208:path=38;5;205'
|
||
|
||
# Matrix: hellgrün und blassgrün
|
||
GOSH_COLORS='user=1;92:path=2;32'
|
||
|
||
# Dracula: lila und pink (Truecolor)
|
||
GOSH_COLORS='user=1;38;2;189;147;249:path=38;2;255;121;198'
|
||
|
||
# Nord: frostblau und hellgrau (Truecolor)
|
||
GOSH_COLORS='user=1;38;2;136;192;208:path=38;2;216;222;233'
|
||
|
||
# Ozean: türkis und kursiv blau
|
||
GOSH_COLORS='user=1;96:path=3;94'
|
||
|
||
# Fehler-Status gelb auf rotem Hintergrund
|
||
GOSH_COLORS='error=1;33;41'
|
||
|
||
# Root-Warnung: weiß auf rotem Hintergrund
|
||
GOSH_COLORS='root=1;97;41'
|
||
|
||
# Dezent: nur das Verzeichnis unterstrichen
|
||
GOSH_COLORS='user=:path=4'
|
||
```
|
||
|
||
### Sicherheitshinweise
|
||
|
||
- Die History liegt in `$XDG_STATE_HOME/gosh/history` (Standard: `~/.local/state/gosh/history`) und ist nur für den eigenen Benutzer lesbar (Verzeichnis `0700`, Datei `0600`). Mit `GOSH_HISTFILE` lässt sich ein anderer Pfad setzen, mit `GOSH_HISTFILE=` wird die History abgeschaltet.
|
||
- Zeilen, die mit einem Leerzeichen beginnen, werden nicht in der History gespeichert – praktisch für Befehle mit Passwörtern oder Tokens.
|
||
- Innerhalb von `'...'` wird nichts expandiert, `\$` ergibt ein wörtliches `$`.
|
||
- Eine nicht gesetzte Variable bricht den Befehl ab (wie `set -u`), damit z. B. `rm -rf $DIR/out` nie mit leerem `$DIR` ausgeführt wird. Für einen Standardwert `${VAR:-default}` bzw. `${VAR:-}` verwenden.
|
||
- Variablenwerte werden nicht in mehrere Wörter aufgeteilt.
|
||
- Steuerzeichen in Verzeichnisnamen werden im Prompt escaped angezeigt (z. B. `\x1b`), Dateien mit Steuerzeichen im Namen werden nicht vervollständigt.
|
||
- Ctrl+C beendet nur den laufenden Befehl bzw. verwirft die aktuelle Zeile, nicht die Shell.
|
||
|
||
_gosh is not meant for production use, it is just a proof of concept._
|
||
|
||
## building
|
||
|
||
```bash
|
||
go build ./
|
||
```
|
||
|
||
Die Version lässt sich mit `gosh -version` (oder `--version`, `-V`) abfragen und steht auch in der Ausgabe von `help`:
|
||
|
||
```
|
||
$ gosh -version
|
||
gosh 0.2.1 (branch: main, commit: 03e791c)
|
||
```
|
||
|
||
Version und Branch werden beim Bauen mit GoReleaser per `-ldflags` gesetzt. Bei einem einfachen `go build` steht dort `dev`, der Commit wird dann aus den von Go eingebetteten Git-Informationen gelesen (mit `-dirty`, falls es uncommittete Änderungen gab).
|
||
|
||
## Release
|
||
|
||
Binaries sowie `.deb`- und `.rpm`-Pakete werden mit [GoReleaser](https://goreleaser.com) gebaut (Konfiguration: `.goreleaser.yaml`).
|
||
|
||
```bash
|
||
# lokal testen, ohne etwas zu veröffentlichen (Ergebnis in dist/)
|
||
goreleaser release --snapshot --clean
|
||
|
||
# Release auf Gitea veröffentlichen
|
||
export GITEA_TOKEN=...
|
||
git tag -a v0.1.0 -m "v0.1.0"
|
||
git push origin v0.1.0
|
||
goreleaser release --clean
|
||
```
|
||
|
||
Das Paket installiert `/usr/bin/gosh` und die Man-Page `gosh(1)` (`man gosh`, Quelle: `packaging/man/gosh.1`, lokal ansehen mit `man ./packaging/man/gosh.1`) und trägt die Shell in `/etc/shells` ein, sodass sie mit `chsh -s /usr/bin/gosh` gesetzt werden kann.
|