add job control

- in an interactive shell every pipeline runs in its own process group
  and gets the terminal while it runs in the foreground
- Ctrl+Z stops the foreground job, command & starts a job in the
  background; finished and stopped jobs are reported before the prompt
- new builtins jobs, fg, bg and wait (wait also works without job
  control, e.g. in scripts, where background jobs read from /dev/null)
- terminal modes are saved and restored when a job stops or continues
- exit and Ctrl+D warn once if there are stopped jobs
- processes are waited for with wait4 (WUNTRACED) instead of exec.Cmd.Wait
- pty based test for job control (Linux)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Florian WaltherandClaude Opus 5.5 committed 2026-10-03 23:22:21 +02:00
1 parent 4b44cc6e72
commit dae4087a94
6 files changed
+990 -73

No files matched your search

+42 -1
View File
@@ -13,6 +13,10 @@ Built-in commands:
set VAR=val - Set environment variable
export VAR=val - Export environment 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
help - Show this help
Features:
@@ -37,6 +41,8 @@ Features:
a || b (b only if a failed); $? is the exit status of the last command
- 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
- 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
@@ -179,7 +185,42 @@ $ grep -c error < build.log
- 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.
- Ein einzelnes `&` (Hintergrundprozesse) gibt es noch nicht, es wird als normales Zeichen übergeben.
### 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`.
- Einen Job beenden lässt sich mit `kill` über die Prozess-ID (z. B. `kill 12345`); `kill %1` versteht gosh noch nicht.
### Farben