add parameter expansion, arithmetic, set -e/-x/pipefail, trap, eval, exec
- ${#v}, ${v#p} ${v##p} ${v%p} ${v%%p}, ${v/p/r} ${v//p/r} ${v/#p/r}
${v/%p/r}, ${v:off:len}, ${v-w} ${v:=w} ${v:+w} ${v:?msg}; nested ${}
- arithmetic expansion $((...)) with the C operators, ** and assignments
- set -e, set -x, set -o pipefail (also as gosh options, passed on to
subshells); set shows variables, set -o the options
- trap for EXIT and signals; scripts with an EXIT trap run it on
SIGINT/SIGTERM/SIGHUP too
- eval, exec (also exec > file without a command), $!, wait pid
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f66cf538c0
commit
1f507f6562
20 files changed
+2408
-93
No files matched your search
@@ -11,14 +11,16 @@ Built-in commands:
|
||||
exit [code] - Exit shell with status code (default: $?)
|
||||
quit [code] - Quit shell with status code (default: $?)
|
||||
set VAR=val - Set environment variable
|
||||
set -- args - Set the positional parameters $1, $2, ...
|
||||
set [-ex] [-o pipefail] [--] [args] - Options: -e exit when a command
|
||||
fails, -x print commands, -o pipefail; args are $1, ...
|
||||
(set: show variables, set -o: show options)
|
||||
export VAR=val - Export environment variable
|
||||
unset VAR - Remove a variable (unset -f name: a function)
|
||||
source file [args] - 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
|
||||
wait [%n|pid] - Wait for job n (or process pid, e.g. $!) or all 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)
|
||||
@@ -30,6 +32,11 @@ Built-in commands:
|
||||
shift [n] - Remove the first n positional parameters (default 1)
|
||||
read [-r] [-p prompt] [VAR ...] - Read a line into variables (split
|
||||
at $IFS; the last one gets the rest; default $REPLY)
|
||||
eval args - Run the arguments as a command in this shell
|
||||
exec [cmd] - Replace the shell with cmd; without cmd, its
|
||||
redirections apply to the shell (exec > log)
|
||||
trap [cmd] SIG... - Run cmd on a signal (INT, TERM, HUP, USR1, ...)
|
||||
or on EXIT; trap '' SIG ignores it, trap - SIG resets
|
||||
help - Show this help
|
||||
|
||||
Features:
|
||||
@@ -57,8 +64,15 @@ Features:
|
||||
a pattern
|
||||
- Environment variable expansion ($VAR, ${VAR}, ${VAR:-default});
|
||||
using an unset variable is an error, use ${VAR:-} for an empty default
|
||||
- Parameter expansion: ${#VAR} (length), ${VAR#pat} ${VAR##pat}
|
||||
${VAR%pat} ${VAR%%pat} (remove prefix/suffix), ${VAR/pat/rep}
|
||||
${VAR//pat/rep}, ${VAR:offset:length}, ${VAR-x} ${VAR:=x} ${VAR:+x}
|
||||
${VAR:?msg}
|
||||
- Arithmetic: $((expression)) with integers and the C operators
|
||||
(+ - * / % ** << >> < == && || ?: = += ++ ...)
|
||||
- Positional parameters: $0, $1 ... $9, ${10}, $# (count), "$@" (one
|
||||
word each), "$*" (one word), $$ (process ID); set by gosh script
|
||||
word each), "$*" (one word), $$ (process ID), $! (process ID of the
|
||||
last background job); set by gosh script
|
||||
args, gosh -c command name args, functions, set -- and source
|
||||
- Command substitution: $(command) is replaced by the output of command
|
||||
(run in a subshell); unquoted it is split into words, "$(command)"
|
||||
@@ -92,8 +106,8 @@ Features:
|
||||
### Aufruf
|
||||
|
||||
```
|
||||
gosh [-l] [-i] [--norc] [--noprofile] [script [args ...]]
|
||||
gosh [-l] [--noprofile] -c command [name [args ...]]
|
||||
gosh [-l] [-i] [-ex] [-o option] [--norc] [--noprofile] [script [args ...]]
|
||||
gosh [-l] [-ex] [-o option] [--noprofile] -c command [name [args ...]]
|
||||
gosh -version
|
||||
```
|
||||
|
||||
@@ -108,6 +122,7 @@ gosh -version
|
||||
| `-l`, `--login` | Login-Shell, liest die Profil-Dateien (siehe unten) |
|
||||
| `--norc` | `goshrc`-Dateien nicht lesen |
|
||||
| `--noprofile` | `profile`-Dateien nicht lesen |
|
||||
| `-e`, `-x`, `-o option` | Optionen wie bei `set` (z. B. `gosh -e skript`, `-o pipefail`) |
|
||||
|
||||
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.
|
||||
|
||||
@@ -175,7 +190,7 @@ for i in {1..5}; do echo "Durchlauf $i"; done
|
||||
n=3
|
||||
while [ $n -gt 0 ]; do
|
||||
echo $n
|
||||
n=$(expr $n - 1)
|
||||
n=$((n - 1))
|
||||
done
|
||||
|
||||
until ping -c1 -W1 server >/dev/null; do sleep 1; done
|
||||
@@ -211,7 +226,7 @@ test -d build || { echo "kein build-Verzeichnis"; exit 1; }
|
||||
- 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.
|
||||
- Arithmetik geht noch nicht mit `$((…))`, sondern z. B. mit `n=$(expr $n + 1)`.
|
||||
- Zum Rechnen in Bedingungen und Schleifen siehe [Arithmetik](#arithmetik): `while [ $i -lt 10 ]; do i=$((i + 1)); done`.
|
||||
|
||||
### Umleitungen, Pipes und Hintergrund bei Kontrollstrukturen
|
||||
|
||||
@@ -305,6 +320,7 @@ ls | zeilen
|
||||
| `"$@"` | alle Argumente, jedes als eigenes Wort |
|
||||
| `"$*"` | alle Argumente als ein Wort, durch Leerzeichen getrennt |
|
||||
| `$$` | Prozess-ID der Shell |
|
||||
| `$!` | Prozess-ID des letzten Hintergrund-Jobs (`wait $!`, `kill $!`) |
|
||||
|
||||
```bash
|
||||
#!/usr/bin/gosh
|
||||
@@ -367,6 +383,60 @@ echo $name ${name:-standard}
|
||||
- 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.
|
||||
|
||||
### Parameter-Expansion
|
||||
|
||||
```bash
|
||||
datei=/home/flo/bilder/urlaub.tar.gz
|
||||
echo ${#datei} # 30 Länge
|
||||
echo ${datei##*/} # urlaub.tar.gz wie basename
|
||||
echo ${datei%/*} # /home/flo/bilder wie dirname
|
||||
echo ${datei%%.*} # /home/flo/bilder/urlaub
|
||||
echo ${datei#*.} # tar.gz
|
||||
echo ${datei/flo/anna} # /home/anna/bilder/urlaub.tar.gz
|
||||
echo ${datei//\//:} # :home:flo:bilder:urlaub.tar.gz
|
||||
echo ${datei:0:5} # /home
|
||||
echo ${port:-8080} # Standardwert, wenn port leer oder nicht gesetzt
|
||||
: ${EDITOR:=vim} # Standardwert setzen
|
||||
echo ${1:?datei fehlt} # Fehler, wenn kein Argument
|
||||
```
|
||||
|
||||
| Ausdruck | ergibt |
|
||||
|-----------------------|---------------------------------------------------------------------|
|
||||
| `${#name}` | Länge des Werts in Zeichen |
|
||||
| `${name#muster}` | Wert ohne das kürzeste passende Präfix (`##`: das längste) |
|
||||
| `${name%muster}` | Wert ohne das kürzeste passende Suffix (`%%`: das längste) |
|
||||
| `${name/muster/neu}` | ersetzt den ersten (längsten) Treffer, `//` alle, `/#` nur am Anfang, `/%` nur am Ende |
|
||||
| `${name:anfang}`, `${name:anfang:länge}` | Teilstring; negativer Anfang zählt vom Ende (`${x: -2}`), negative Länge lässt Zeichen am Ende weg |
|
||||
| `${name:-wort}` | `wort`, wenn `name` nicht gesetzt oder leer ist, sonst der Wert |
|
||||
| `${name:=wort}` | wie `:-`, setzt `name` aber auch auf `wort` |
|
||||
| `${name:+wort}` | `wort`, wenn `name` gesetzt und nicht leer ist, sonst nichts |
|
||||
| `${name:?meldung}` | Fehler mit `meldung`, wenn `name` nicht gesetzt oder leer ist |
|
||||
| `${name-wort}` usw. | ohne `:`: nur „nicht gesetzt“ zählt, ein leerer Wert nicht |
|
||||
|
||||
- Das Wort bzw. Muster wird expandiert (Variablen, `$(…)`, `$((…))`, Anführungszeichen, `~`), aber nicht in Wörter geteilt. Verschachteln geht: `${a:-${b:-c}}`.
|
||||
- Muster sind Wildcards wie bei Dateinamen (`*`, `?`, `[…]`), nur passen `*` und `?` auch auf `/`. Zeichen in Anführungszeichen und Werte von Variablen passen nur wörtlich (wie überall in gosh): `${x%"*"}` entfernt einen `*` am Ende, `${x%$suffix}` den Text von `$suffix`.
|
||||
- Mit `+`, `-`, `?` und `=` gibt es keinen Fehler für nicht gesetzte Variablen, alle anderen Formen melden `unbound variable`.
|
||||
- Längen und Teilstrings zählen Zeichen, nicht Bytes (`${#x}` für `Grüße` ist 5).
|
||||
- `${name:?…}` bricht nur den Befehl ab (wie eine nicht gesetzte Variable), mit `set -e` endet damit das Skript.
|
||||
|
||||
### Arithmetik
|
||||
|
||||
```bash
|
||||
echo $((1 + 2 * 3)) # 7
|
||||
n=$((n + 1)) # hochzählen
|
||||
: $((n++)) # dasselbe mit ++
|
||||
echo $(( (a + b) / 2 ))
|
||||
echo $((x > 5 ? 1 : 0))
|
||||
echo $((0x1F)) $((2 ** 10)) $((7 % 3))
|
||||
i=0; while [ $i -lt 10 ]; do i=$((i + 1)); done
|
||||
```
|
||||
|
||||
- `$((ausdruck))` rechnet mit ganzen 64-Bit-Zahlen. Es gibt die Operatoren aus C: `+ - * / %`, `**` (Potenz), `<< >>`, `< <= > >= == !=`, `& | ^ ~`, `! && ||`, `? :`, Zuweisungen `= += -= *= /= %= <<= >>= &= |= ^=`, `++` und `--` (davor und danach) sowie `,`.
|
||||
- Variablen lassen sich mit und ohne `$` verwenden (`$((n + 1))`, `$(($n + 1))`). Ist ihr Wert selbst ein Ausdruck, wird er ausgerechnet; ein leerer Wert zählt als 0, eine nicht gesetzte Variable ist ein Fehler.
|
||||
- Zahlen sind dezimal, `0x…` hexadezimal oder mit führender `0` oktal (`010` ist 8).
|
||||
- Division durch 0 ist ein Fehler. Bei `&&`, `||` und `? :` wird die nicht benötigte Seite nicht ausgerechnet.
|
||||
- Das Ergebnis ist ein Wort (auch ohne Anführungszeichen). `((…))` als eigener Befehl und `let` gibt es nicht, `: $((…))` tut das Gleiche.
|
||||
|
||||
### Befehlsersetzung
|
||||
|
||||
`$(befehl)` wird durch die Ausgabe von `befehl` ersetzt:
|
||||
@@ -385,7 +455,7 @@ $ echo "Dateien: $(ls | wc -l)"
|
||||
- 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.
|
||||
- In `'...'` oder mit `\$(` wird nichts ersetzt. Die alte Schreibweise mit Backticks (`` `befehl` ``) gibt es nicht. `$((…))` ist keine Befehlsersetzung, sondern [Arithmetik](#arithmetik); eine Subshell darin braucht ein Leerzeichen: `$( (befehl) )`.
|
||||
|
||||
### Befehlslisten und Exit-Status
|
||||
|
||||
@@ -411,6 +481,65 @@ 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.
|
||||
|
||||
### Fehlerbehandlung: set -e, set -x, trap
|
||||
|
||||
```bash
|
||||
#!/usr/bin/gosh
|
||||
set -euo pipefail # abbrechen bei Fehlern, auch in Pipes
|
||||
|
||||
tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$tmp"' EXIT # aufräumen, auch bei Fehlern oder Ctrl+C
|
||||
|
||||
cd "$tmp"
|
||||
curl -fsSO https://example.org/paket.tar.gz
|
||||
tar xzf paket.tar.gz
|
||||
```
|
||||
|
||||
| Option | Bedeutung |
|
||||
|----------------------------|-------------------------------------------------------------------|
|
||||
| `set -e` (`-o errexit`) | beendet die Shell, wenn ein Befehl fehlschlägt |
|
||||
| `set -x` (`-o xtrace`) | gibt jeden Befehl nach der Expansion mit `+ ` davor auf stderr aus |
|
||||
| `set -o pipefail` | Status einer Pipe ist der des letzten fehlgeschlagenen Befehls |
|
||||
| `set -u` (`-o nounset`) | ist in gosh immer an, `set +u` gibt es nicht |
|
||||
| `set +e`, `set +x`, … | schaltet die Option wieder aus |
|
||||
| `set -o` / `set +o` | zeigt die Optionen an (`set +o` als Befehle zum Wiederherstellen) |
|
||||
|
||||
- `set -e` gilt nicht für Bedingungen von `if`, `while` und `until`, für Befehle mit `!` und für alle Befehle einer `&&`/`||`-Liste außer dem letzten: `test -f x || echo fehlt` bricht nicht ab. Es gilt auch in Funktionen, Schleifen, Subshells und `$(…)`, ein fehlgeschlagenes `x=$(befehl)` beendet das Skript also.
|
||||
- Die Optionen lassen sich auch beim Aufruf setzen (`gosh -ex skript`, `gosh -o pipefail -c '…'`) und gelten in Subshells weiter.
|
||||
- `set` ohne Argumente zeigt alle Variablen an. `set a b c` und `set -e a b` setzen außerdem die Positionsparameter, `set VAR=wert` setzt wie bisher eine Variable.
|
||||
|
||||
`trap befehl signal …` führt `befehl` aus, wenn das Signal kommt:
|
||||
|
||||
| Aufruf | Bedeutung |
|
||||
|----------------------------|-------------------------------------------------------------------|
|
||||
| `trap 'befehl' EXIT` | beim Beenden der Shell (Ende des Skripts, `exit`, Fehler mit `set -e`, Ctrl+C, `kill`) |
|
||||
| `trap 'befehl' INT TERM` | bei den Signalen (`HUP`, `INT`, `QUIT`, `TERM`, `USR1`, `USR2`, `ALRM`, `WINCH`, auch mit `SIG` davor oder als Nummer) |
|
||||
| `trap '' INT` | ignoriert das Signal, auch in gestarteten Programmen |
|
||||
| `trap - INT` | stellt das normale Verhalten wieder her |
|
||||
| `trap`, `trap -p [sig]` | zeigt die Traps an |
|
||||
| `trap -l` | listet die Signale |
|
||||
|
||||
- Der Befehl läuft in der Shell (er kann Variablen sehen und `exit` aufrufen), und zwar nach dem Befehl, der gerade läuft, wie in `sh`. `$?` ist im Trap der Status davor, beim `EXIT`-Trap der Exit-Status der Shell.
|
||||
- Ein Skript mit `EXIT`-Trap führt ihn auch aus, wenn es durch Ctrl+C, `SIGTERM` oder `SIGHUP` beendet wird, und endet danach mit diesem Signal. Mit einem eigenen Trap für das Signal (`trap 'echo abgebrochen; exit 1' INT`) läuft stattdessen dieser Trap. Endet er nicht mit `exit`, läuft das Skript nach dem abgebrochenen Befehl weiter.
|
||||
- In der interaktiven Shell geht Ctrl+C mit Job-Control nur an den laufenden Befehl, ein `INT`-Trap wird dabei nicht ausgelöst.
|
||||
- Subshells (`( … )`, `$(…)`, Pipes) erben keine Traps, ignorierte Signale bleiben aber ignoriert.
|
||||
|
||||
### eval und exec
|
||||
|
||||
```bash
|
||||
cmd="ls -l | head -3"
|
||||
eval "$cmd" # führt den Text als Befehl aus
|
||||
eval "wert_$name=42" # Variablennamen zusammensetzen
|
||||
|
||||
exec > build.log 2>&1 # ab hier geht alle Ausgabe in die Datei
|
||||
exec < eingabe.txt # stdin der Shell umleiten
|
||||
exec ssh server # gosh durch ssh ersetzen
|
||||
```
|
||||
|
||||
- `eval` fügt seine Argumente mit Leerzeichen zusammen und führt das Ergebnis in der laufenden Shell aus, mit allen Expansionen ein zweites Mal. In einer Pipe oder im Hintergrund läuft es in einer Subshell.
|
||||
- `exec befehl` ersetzt gosh durch `befehl` (gleiche Prozess-ID, Variablen vor dem Befehl gelten für ihn). Wird der Befehl nicht gefunden, beendet sich ein Skript mit Status 127, die interaktive Shell meldet nur den Fehler.
|
||||
- `exec` ohne Befehl wendet seine Umleitungen dauerhaft auf die Shell an: `exec > log` leitet ab dort alle Ausgaben um, `exec >&2` bzw. `exec > /dev/tty` wieder zurück.
|
||||
|
||||
### Klammer-Expansion
|
||||
|
||||
Wie in bash erzeugt `{…}` aus einem Wort mehrere Wörter:
|
||||
@@ -589,7 +718,8 @@ $ bg %2 # oder: angehaltenen Job im Hintergrund weiterlauf
|
||||
- `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.
|
||||
- Einen Job beenden lässt sich mit `kill` über die Prozess-ID (z. B. `kill 12345` oder `kill $!` für den letzten Hintergrund-Job); `kill %1` versteht gosh noch nicht.
|
||||
- `wait` wartet auch auf eine Prozess-ID: `befehl & pid=$!; …; wait $pid` liefert den Exit-Status von `befehl`. Eine Zahl ohne `%` ist zuerst eine Prozess-ID, sonst eine Job-Nummer.
|
||||
|
||||
### Prompt
|
||||
|
||||
|
||||
Reference in new issue
Block a user