leestijd : 10 minutes

U lanceert`./gradlew build`, de terminal is geblokkeerd gedurende 3 minuten, en je moet iets anders doen terwijl je wacht ? Je hoeft geen nieuw tabblad te openen.`Ctrl+Z`schorst het proces,`fg`Het brengt het precies terug waar je het liet. Dit is het meest onderschatte Unix-tip in de dagelijkse routine van een ontwikkelaar.

Het probleem: een bezet terminal

Elke ontwikkelaar heeft deze situatie meegemaakt:

$ ./gradlew build
> Building...

De terminal is geblokkeerd. Je kunt geen commando’s meer typen. En u moet een bestand bekijken, een starten.git status, of een bezette poort controleren.

@startuml
skinparam backgroundColor #FEFEFE

actor Développeur as dev
participant "Terminal" as term
participant "Gradle\n( proces )" as gradle

dev -> term : ./gradlew bouwen
term -> gradle : Start de build
gradle -> term : Vul de terminal\n(zonder prompt)
dev -> term : wil een opdracht typen
term --> dev : Geblokkeerde terminal

note right of dev
De ontwikkelaar is
verlamd gedurende
de volledige duur van de build
end note
@enduml

De meeste ontwikkelaars openen een nieuwe terminal. Maar er bestaat een elegantere, snellere en natieve oplossing: het beheer van taken.

De drie onmisbare opdrachten

Ctrl+Z — Tijdelijk stoppen (SIGTSTP)

`Ctrl+Z`verzend het signaal`SIGTSTP`naar het proces in de voorgrond. Het proces isopgehangen— niet gedood — en het terminal krijgt zijn prompt terug.

$ ./gradlew build
> Building...
^Z
[1]+  Stoppé    ./gradlew build
$

Het getal tussen haakjes`[1]`is dejobnummer. Le `+`geeft de standaardtaak aan.

fg — Voortzetten op de voorgrond

`fg`brengt de gepauzeerde taak naar de voorgrond. Het proces hervat precies waar het was gebleven.

$ fg
./gradlew build
> Building... (reprend où il en était)

Met een specifiek jobnummer:

$ fg %2

bg — Hervatten op de achtergrond

`bg`herneem het proces zonder de terminal te blokkeren. Ideaal voor lange builds.

$ bg
[1]+ ./gradlew build &
$

Le `&`Aan het einde betekent dat het proces op de achtergrond draait.

jobs — Toon de taken van de shell

$ jobs
[1]-  Stoppé    vim README.md
[2]+  En cours   ./gradlew build
@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif

state "Voorgrond\n(geblokkeerde terminal)" as fg #LightCoral
state "Opgeschort\n(Ctrl+Z)" as stopped #LightYellow
state "Achtergrond\n(achtergrond)" as bg_ #LightGreen

[*] --> fg : Lance une commande
fg --> stopped : Ctrl+Z\n(SIGTSTP)
stopped --> fg : fg\nreprend op de voorgrond
stopped --> bg_ : bg\ngaat verder op de achtergrond
bg_ --> fg : fg\nbrengt naar de voorgrond
fg --> [*] : Bestelling voltooid

note right of stopped
Het proces is bevroren
maar _pas_ gedood
end note

note left of bg_
Het terminal is vrij
de bouw doorgaat
end note
@enduml

Scenario 1 : De lange build

Dit is het meest voorkomende gebruiksgeval. Je start een Gradle-build, de terminal is geblokkeerd, en je moet iets anders doen.

@startuml
skinparam backgroundColor #FEFEFE
autonumber

actor Développeur as dev
participant "Terminal" as term
participant "Gradle" as gradle
database "Status\nproces" as state

dev -> term : ./gradlew bouw
term -> gradle : Start de build
gradle -> state : Bezig (FG)
dev -> term : Ctrl+Z
term -> state : Geschorst (job [1])
term --> dev : Vrije prompt $

dev -> term : git status
term --> dev : werkboom schoon

dev -> term : fg
term -> state : Hervat (FG)
gradle --> dev : Bouw geslaagd ✓

note right of state
Het proces Gradle
is nooit gedood
Het hervat precies
waar het was gebleven
end note
@enduml

Stap voor stap

$ ./gradlew build
> Building...
^Z
[1]+  Stoppé    ./gradlew build

$ git status
On branch main
nothing to commit, working tree clean

$ fg
./gradlew build
> Building...
BUILD SUCCESSFUL in 2m 34s

Scenario 2 : De vergeten redacteur

U bent in`vim`, u moet een ander bestand raadplegen zonder te vertrekken`vim`.

# Dans vim, en mode commande :
:shell
$ cat /path/to/other/file.txt
$ exit
# Retour dans vim

Maar het is zwaar. Met`Ctrl+Z`, het is onmiddellijk :

# Dans vim, n'importe quel moment :
Ctrl+Z
[1]+  Stoppé    vim README.md

$ cat /path/to/other/file.txt
# ... lire le fichier ...

$ fg
vim README.md
# Retour exact là où on en était, curseur en place
@startuml
skinparam backgroundColor #FEFEFE
autonumber

actor Développeur as dev
participant "vim" as vim
participant "Shell" as shell

dev -> vim : bewerking in uitvoering
dev -> vim : Ctrl+Z
vim -> shell : Geschorst (taak [1])
dev -> shell : kat autre_fichier.txt
shell --> dev : inhoud van het bestand
dev -> shell : fg
shell -> vim : Continueer (job [1])
vim --> dev : Cursor precies\nop de dezelfde positie

note right of vim
vim herstelt
intvolledig de staat :
positie van de cursor,
buffer, ongedaan maken stapel
end note
@enduml

vim`herstelt volmaakt zijn staat na een`fg: cursorpositie, bufferinhoud, geschiedenis van ongedaan maken — alles wordt bewaard.

Scenario 3: Het proces opencode is onderbroken

Dit is het scenario dat dit artikel heeft gemotiveerd. U gebruikt`opencode`(CLI-hulpmiddel om een LLM op je code te besturen), en het proces is opgeschort.

@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif
skinparam componentStyle rectangle

actor Développeur as dev
participant "opencode\n(LLM agent)" as agent
participant "Shell" as shell
participant "git" as git

dev -> agent : Actieve sessie
agent -> agent : Tour de parole\ndu LLM en cours

note over agent
Ctrl+Z verschijnt
midden in
een code-analyse
end note

dev -> agent : Ctrl+Z
agent -> shell : Suspendu (job [1])
dev -> git : git diff
git --> dev : zichtbare veranderingen
dev -> shell : fg
shell -> agent : Hervat (job [1])
agent --> dev : Reprend exactement\nl'analyse en cours

note right of agent
Geen verlies van context
LLM hervat zijn antwoord
Exact daar waar het was gebleven
end note
@enduml
$ opencode
# ... session en cours, LLM analyse le code ...
^Z
[1]+  Stoppé    opencode

$ git diff HEAD~1
# vérifier les changements récents

$ fg
opencode
# Le LLM reprend là où il s'était arrêté

Het voordeel is dubbel:

  1. Geen contextverlies: de LLM-sessie, de gespreksgeschiedenis, de geopende bestanden — alles wordt behouden

  2. Geen herstart: geen noodzaak om opencode opnieuw te starten, de context opnieuw te laden, het probleem aan de LLM uit te leggen

Scenario 4: Meerdere taken tegelijkertijd beheren

fg et `bg`accepteren een jobnummer om een specifiek proces te targeten wanneer meerdere zijn opgeschort

$ vim config.yml
^Z
[1]+  Stoppé    vim config.yml

$ ./gradlew test
^Z
[2]+  Stoppé    ./gradlew test

$ htop
^Z
[3]+  Stoppé    htop

$ jobs
[1]   Stoppé    vim config.yml
[2]-  Stoppé    ./gradlew test
[3]+  Stoppé    htop

$ fg %2
./gradlew test
# Le build reprend en avant-plan

$ bg %3
[3] htop &
# htop reprend en arrière-plan
@startuml
skinparam backgroundColor #FEFEFE

rectangle "Gestopte taken" as jobs #LightYellow {
  card "[1] vim config.yml" as j1
  card "[2] ./gradlew test" as j2
  card "[3] htop" as j3
}

rectangle "Acties" as actions #LightGreen {
  card "fg %2\n→ voorgrond" as a1
  card "bg %3\n→ achtergrond" as a2
  card "fg %1\n→ voorgrond" as a3
}

jobs --> actions : fg/bg + jobnummer
actions --> j2 : Hervat
actions --> j3 : Op de achtergrond

note bottom of jobs
Elke taak behoudt
zijn exacte staat
tussen de suspend/resume
end note
@enduml

Overzicht van de jobopdrachten

Bestelling effect Voorbeeld

Ctrl+Z

Suspendeer het proces op de voorgrond

Verzending van`SIGTSTP`

fg

Hervat de standaardtaak in de voorgrond

fg ou fg %1

fg %n

Hervat de job n op de voorgrond

fg %2

bg

Neem de standaardtaak op de achtergrond

bg ou bg %1

bg %n

Hervat job n op de achtergrond

bg %2

jobs

Lijst de taken van de huidige shell

jobs -l(met PID)

kill %n

Voltooi de taak n

kill %2

Verschillen tussen fg, bg, Ctrl+Z, Ctrl+C en &

Veel ontwikkelaars verwarren deze mechanismen. Dit is een cruciale verduidelijking.

@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif

rectangle "**Ctrl+Z**\nSIGTSTP" as ctrlz #LightYellow {
  card "Schors het proces\n(hij staat nog steeds in het geheugen)\nGebruikbaar met fg/bg" as c1
}

rectangle "**Ctrl+C**\nSIGINT" as ctrlc #LightCoral {
  card "Dood het proces\n(geen terugkeer mogelijk)\nVerlies van de volledige staat" as c2
}

rectangle "**& **\n(arrière-plan)" as ampersand #LightGreen {
  card "Start meteen\nop de achtergrond\nTerminal meteen vrij" as c3
}

rectangle "**fg**\n(foreground)" as fg_ #SkyBlue {
  card "Brengt een taak\ngepauzeerd op de voorgrond\nTerminal bezet" as c4
}

rectangle "**bg**\n(background)" as bg_ #Lavender {
  card "Hervat een taak\ngepauzeerd op de achtergrond\nTerminal vrij" as c5
}

ctrlz -right-> fg_ : fg om te hervatten
ctrlz -right-> bg_ : bg om verder te gaan\nop de achtergrond
ctrlc -right-> c2 : Geen retour\nproces gedood

note bottom of ctrlz
Het proces is **vastgelopen**
maar **niet gedood**
end note

note bottom of ampersand
Alternatief: start ./gradlew build & direct op de achtergrond
end note
@enduml
Sneltoets/Commando signaal Effect Teruggaan?

Ctrl+Z

SIGTSTP

Schors het proces (bevroren, in het geheugen)

Ja:`fg` ou bg

Ctrl+C

SIGINT

Stop het proces (afgerond)

Nee : vernietigd proces

Ctrl+\

SIGQUIT

Stop het proces + core dump

Nee: proces vernietigd

commande &

—

Start direct in de achtergrond

`fg`om het naar voren te brengen

Ctrl+Z`isniet-destructief. Het proces is vastgehouden zoals het is, in het geheugen. U kunt direct verdergaan met`fg. Het is een pause, geen stop.

De volledige levenscyclus van een job

@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif

title Volledige levenscyclus van een shell-job
state "Niet bestaand" as none
state "Voorgrond\n(neemt de terminal in beslag)" as foreground #LightCoral
state "Gepauzeerd\n(Ctrl+Z / SIGTSTP)" as suspended #LightYellow
state "Achtergrond\n(bg of &)" as background #LightGreen
state "Afgerond\n(afsluitcode 0)" as done #LightGray

[*] --> none
none --> foreground : typ een opdracht
foreground --> suspended : Ctrl+Z
foreground --> done : normaal einde

suspended --> foreground : fg
suspended --> background : (Empty)
suspended --> done : kill %n

background --> foreground : fg
background --> done : einde normaal\n(op de achtergrond)
background --> suspended : Ctrl+Z\n(als fg dan Ctrl+Z)

done --> [*]

note right of suspended
De opgeschorte staat is
het draaipunt van het hele
jobsysteem
end note
@enduml

Geavanceerde tips

fg met de meest recente baan en de afwisseling

fg`zonder argument brengt de gemarkeerde taak+(het meest recente). Maar je kunt ook gebruiken%-`om op de vorige taak te richten :

$ vim file1.txt
^Z
[1]+  Stoppé    vim file1.txt

$ vim file2.txt
^Z
[2]+  Stoppé    vim file2.txt

$ fg      # ramène job [2] (le plus récent)
$ fg %-   # ramène job [1] (le précédent)

een job netjes afmaken

$ jobs
[1]-  Stoppé    vim config.yml
[2]+  Stoppé    ./gradlew test

$ kill %2       # envoie SIGTERM au job 2
$ kill -9 %1    # envoie SIGKILL au job 1 (force)

désown : loskoppelen van de shell

disown`Verwijder een job uit de job-tabel van de shell. Het proces blijft draaien maar kan niet meer worden teruggehaald met`fg:

$ ./gradlew build &
[1] 12345

$ disown %1
# Le processus continue mais n'est plus lié au shell
# Ça survives à la fermeture du terminal

Combineer met nohup voor langdurige processen

Zodat een proces blijft bestaan na het sluiten van de terminal:

$ nohup ./gradlew build &
[1] 12345
$ disown %1
# Le build continue même si vous fermez le terminal
@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif

rectangle "fg" as fg_ #SkyBlue {
  card "job\nhet meest recente (+)" as f1
  card "Richt een job\nspecifiek : fg %n" as f2
  card "Wissel : fg %-" as f3
}

rectangle "kill %n" as kill_ #LightCoral {
  card "SIGTERM (net)" as k1
  card "SIGKILL -9 (dwang)" as k2
}

rectangle "disown" as disown #Lavender {
  card "Maakt los van de shell\n(blijft bestaan na afsluiting)" as d1
}

rectangle "nohup + &" as nohup_ #LightGreen {
  card "Proces ongevoelige\ntegen SIGHUP\n(sluiting\nde terminal)" as n1
}

fg_ -right-> kill_ : als moet\nvoltooien
kill_ -right-> disown_ : of losmaken
nohup_ -left-> fg_ : fg haalt en\navant-plan

note bottom of disown_
Na `disown` kan `fg` de taak niet meer naar voren brengen.
end note
@enduml

Valkuilen en veelvoorkomende fouten

Ctrl+Z en Ctrl+C verwarren

Dit is de meest voorkomende fout.`Ctrl+C`doodt het proces.`Ctrl+Z`Het opschort. Als je drukt op`Ctrl+C`Reflexmatig is de hele staat verloren — geopende bestanden, LLM-sessies, lopende builds.

@startuml
skinparam backgroundColor #FEFEFE

rectangle "Ctrl+Z (SIGTSTP)" as z #LightGreen {
  card "Proces bevroren in het geheugen" as z1
  card "fg brengt het intact terug" as z2
  card "Geen verlies" as z3
}

rectangle "Ctrl+C (SIGINT)" as c #LightCoral {
  card "Proces beëindigd" as c1
  card "Toestand definitief verloren" as c2
  card "Geen terugkeer mogelijk" as c3
}

note bottom of z
Pauze → Hervatten
end note

note bottom of c
Kill → Verloren
end note
@enduml

De jobs zijn lokaal voor de shell

jobs, fg et `bg`Ze werken alleen in de shell die de processen heeft gestart. Als je een nieuw terminal opent, zie je de jobs van de ander niet.

# Terminal 1
$ vim file.txt
^Z
[1]+  Stoppé    vim file.txt

# Terminal 2 (nouveau)
$ jobs
# (rien — les jobs sont locaux au shell)

Om de systeemwijdere processen te zien, gebruik`ps` ou htop:

ps aux | grep vim

Jobs overleven de afsluiting van de shell niet

Als je de terminal sluit, worden alle tijdelijk gestopte of op de achtergrond draaiende taken vernietigd. Om een proces in leven te houden, gebruik je`nohup`+disown(Empty output)

$ nohup ./gradlew build &
[1] 12345
$ disown %1
# Fermer le terminal → le build continue

De interactieve processen en fg

Sommige interactieve programma’s doen het niet goed na een`Ctrl+Z`+fg. Het is zeldzaam maar het gebeurt:

  • Sommige programma’s die signalen gebruiken (vim, htop, less hanteren goed`Ctrl+Z`)

  • Lange netwerkprogramma’s (ssh, opencode) herstellen zich meestal correct

  • Programma’s die de terminal (ncurses) wijzigen, kunnen soms de terminal in een onverwachte staat achterlaten.

In geval van een beschadigde terminal na een`fg`, typ :

reset

Overzichtsdiagram

@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif
skinparam ArrowColor #444444

title "fg — De tip die alles verandert"

rectangle "**Probleem**" as problem #LightCoral {
  card "Terminal geblokkeerd\ndoor een proces" as p1
  card "Nieuwe terminal openen = omslachtige oplossing" as p2
}

rectangle "**Oplossing**" as solution #LightGreen {
  card "Ctrl+Z → pauzeren" as s1
  card "fg → hervatten" as s2
  card "bg → op de achtergrond" as s3
  card "jobs → gepauzeerde processen bekijken" as s4
}

rectangle "**Voordelen**" as advantages #SkyBlue {
  card "Geen verlies van context" as a1
  card "Geen herstart nodig" as a2
  card "Eén terminal is voldoende" as a3
}

problem -right-> solution : "De native Unix-oplossing\nvan 40 jaar geleden"
solution -right-> advantages : "Resultaat"

note bottom of advantages
fg is geen obscure tip
Het is een fundamenteel mechanisme
van de Unix shell
end note
@enduml

Waarom fg onderschat is

De meeste moderne ontwikkelaars ontdekken`fg`laat, of nooit. Enkele redenen :

  1. IDE’s verbergen de terminal: VS Code biedt een ingebouwde terminal, maar meerdere tabbladen maken`fg`schijnbaar minder nodig

  2. Grafische tools vervangen de CLI: bestandsbeheerders, systeemmonitors, grafische Git-clients

  3. tmux en screen: deze multiplexers lossen hetzelfde probleem op een andere manier op, maar`fg`is eenvoudiger en altijd beschikbaar

Toch,`fg`is :

  1. universeel: aanwezig in alle POSIX-shells (bash, zsh, fish…​)

  2. Zonder afhankelijkheid: niet nodig om tmux of screen te installeren

  3. momentopname: twee slagen (Ctrl+Z`dan`fg) voor opschorten en hervatten

  4. Contextbehoud: de staat van het proces is intact

@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif

rectangle "Waarom is `fg`\nonderschat?" as why #LightCoral {
  card "IDE met terminal\nmet meerdere tabbladen" as w1
  card "Grafische tools\ndie de CLI vervangen" as w2
  card "tmux/screen\nlossen het probleem\nop een andere manier op" as w3
}

rectangle "Waarom is `fg`\nsuperieur?" as superior #LightGreen {
  card "Universeel: bash,\nzsh, fish, sh..." as s1
  card "Zonder afhankelijkheid:\ngeen installatie" as s2
  card "Twee toetsaanslagen:\nCtrl+Z en dan `fg`" as s3
  card "Context behouden:\nnul verlies van status" as s4
}

why -right-> superior : "Eenvoud\nwint altijd"
@enduml

Technisch geheugen: de signalen

Voor de nieuwsgierigen, hier zijn de betrokken signalen :

signaal Naam Verzonden door Effect

2

SIGINT

Ctrl+C

Onderbreking — beëindigt het proces

3

SIGQUIT

Ctrl+\

Verlaat met core dump

18

SIGTSTP

Ctrl+Z

Interactieve stop — schort het proces op

19

SIGCONT

fg/bg

Doorgaan — hervat een onderbroken proces

15

SIGTERM

kill %n

zuivere beëindiging

9

SIGKILL

kill -9 %n

Geforceerde beëindiging (kan niet worden onderschept)

De volledige stroom :

@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif

participant "Gebruiker" as user
participant "Shell" as shell
participant "Proces" as proc

user -> shell : Ctrl+Z
shell -> proc : SIGTSTP (signal 18)
note right of proc : Bevroren proces\nin het geheugen

user -> shell : fg
shell -> proc : SIGCONT (signal 19)
note right of proc : Proces wordt hervat\nop de voorgrond

user -> shell : bg
shell -> proc : SIGCONT (signal 19)
note right of proc : Proces wordt hervat\nop de achtergrond

user -> shell : Ctrl+C
shell -> proc : SIGINT (signal 2)
note right of proc : Proces voltooid\ntoestand verloren
@enduml

Conclusie

`fg`Het is geen duistere tip — het is een fundamenteel mechanisme van de Unix-shell, beschikbaar sinds de jaren 70. Het feit dat de meeste ontwikkelaars het negeren, is een symptoom van onze tijd: IDE’s en multiplexers hebben ons van de basis verwijderd.

Maar wanneer u op een SSH-server bent, in een minimale terminal, of u gewoon aan het gebruiken bent`opencode`en dat de LLM volop aan het werk is —Ctrl+Z, fg, bg, `jobs`zijn je beste vrienden.

_ Ctrl+Z schorst, fg hervat. Dit is de pauze-speel van de terminal. _

Referenties

Gerelateerde artikelen