tempo de leitura : 10 minutes

Você lança`./gradlew build`, o terminal está bloqueado por 3 minutos, e você precisa fazer outra coisa aguardando? Não é necessário abrir uma nova aba.`Ctrl+Z`suspende o processo,`fg`o traz exatamente para onde você o deixou. É a dica Unix mais subestimada do dia a dia de um desenvolvedor.

O problema: um terminal ocupado

Todo desenvolvedor já passou por essa situação:

$ ./gradlew build
> Building...

O terminal está bloqueado. Você não pode mais digitar nenhum comando. E você precisa consultar um arquivo, executar um`git status`, ou verificar uma porta ocupada.

@startuml
skinparam backgroundColor #FEFEFE

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

dev -> term : ./gradlew compilação
term -> gradle : Execute o build
gradle -> term : Ocupa o terminal\n(sem prompt)
dev -> term : quer digitar um comando
term --> dev : Terminal bloqueado

note right of dev
O desenvolvedor está
paralisado durante
toda a duração do build
end note
@enduml

A maioria dos desenvolvedores abre um novo terminal. Mas existe uma solução mais elegante, mais rápida e nativa: o gerenciamento de jobs.

Os três comandos indispensáveis

Ctrl+Z — Suspender (SIGTSTP)

`Ctrl+Z`envia o sinal`SIGTSTP`ao processo em primeiro plano. O processo ésuspenso— não morto — e o terminal recupera o seu prompt.

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

O número entre colchetes`[1]`é onúmero do emprego. Le `+`indica o trabalho padrão

fg — Retomar em primeiro plano

`fg`Traz o trabalho suspenso para o primeiro plano. O processo retoma exatamente onde havia parado.

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

Com um número de trabalho específico :

$ fg %2

bg — Retomar em segundo plano

`bg`Retoma o processo sem bloquear o terminal. Ideal para builds longos.

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

Le `&`no final significa que o processo está sendo executado em segundo plano.

jobs — Listar os jobs do shell

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

state "Primeiro-plano\n(terminal bloqué)" as fg #LightCoral
state "Suspenso\n(Ctrl+Z)" as stopped #LightYellow
state "Fundo\n(bg)" as bg_ #LightGreen

[*] --> fg : Lance une commande
fg --> stopped : Ctrl+Z\n(SIGTSTP)
stopped --> fg : fg\nretoma em primeiro plano
stopped --> bg_ : bg\ncontinua em segundo plano
bg_ --> fg : fg\ntraz ao primeiro plano
fg --> [*] : Comando concluído

note right of stopped
O processo está congelado
mas _não_ morto
end note

note left of bg_
O terminal está livre
o build continua
end note
@enduml

Cenário 1: o build longo

É o caso de uso mais comum. Você inicia um build Gradle, o terminal está bloqueado, e você precisa fazer outra coisa.

@startuml
skinparam backgroundColor #FEFEFE
autonumber

actor Développeur as dev
participant "Terminal" as term
participant "Gradle" as gradle
database "Estado\nprocesso" as state

dev -> term : ./gradlew construir
term -> gradle : Inicia o build
gradle -> state : Em andamento (FG)
dev -> term : Ctrl+Z
term -> state : Suspenso (job [1])
term --> dev : Prompt livre $

dev -> term : git status
term --> dev : árvore de trabalho limpa

dev -> term : fg
term -> state : Retoma (FG)
gradle --> dev : Build bem-sucedido ✓

note right of state
O processo Gradle
Nunca foi morto
Ele retoma exatamente
onde ele havia parado
end note
@enduml

passo a passo

$ ./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

Cenário 2: O editor esquecido

Você está em`vim`, você precisa consultar outro arquivo sans sair`vim`.

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

Mas é pesado. Com`Ctrl+Z`, é imediato:

# 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 : edição em curso
dev -> vim : Ctrl+Z
vim -> shell : Suspenso (job [1])
dev -> shell : cat autre_fichier.txt
shell --> dev : conteúdo do arquivo
dev -> shell : fg
shell -> vim : Retoma (job [1])
vim --> dev : Cursor exatamente\nna mesma posição

note right of vim
vim restaure
intcomplètement l'état :
posição do cursor,
buffer, pilha de desfazer
end note
@enduml

NOTA:`vim`restaura perfeitamente o seu estado após um`fg`: posição do cursor, conteúdo do buffer, histórico de desfazer — tudo está preservado.

Cenário 3 : O processo opencode suspenso

Este é o cenário que motivou este artigo. Você utiliza`opencode`(ferramenta CLI para pilotar um LLM no seu código), e o processo está suspenso.

@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 : Sessão ativa
agent -> agent : Tour de parole\ndu LLM en cours

note over agent
Ctrl+Z chega em pleno meio de uma análise de código
end note

dev -> agent : Ctrl+Z
agent -> shell : Suspendu (job [1])
dev -> git : diferença do git
git --> dev : alterações visíveis
dev -> shell : fg
shell -> agent : Retoma (job [1])
agent --> dev : Reprend exactement\nl'analyse en cours

note right of agent
Nenhuma perda de contexto
O LLM retoma sua resposta
exatamente onde ele estava
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é

A vantagem é dupla :

  1. Sem perda de contexto: a sessão LLM, o histórico de conversa, os arquivos abertos — tudo está preservado

  2. Sem reinicialização: não é necessário reiniciar o opencode, recarregar o contexto, explicar novamente o problema ao LLM

Cenário 4: Gerenciar vários trabalhos simultaneamente

fg et `bg`aceitam um número de job para direcionar um processo específico quando vários estão suspensos.

$ 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 "Trabalhos suspensos" as jobs #LightYellow {
  card "[1] vim config.yml" as j1
  card "[2] ./gradlew teste" as j2
  card "[3] htop" as j3
}

rectangle "Ações" as actions #LightGreen {
  card "fg %2\n→ primeiro plano" as a1
  card "bg %3\n→ segundo plano" as a2
  card "fg %1\n→ primeiro plano" as a3
}

jobs --> actions : fg/bg + número do trabalho
actions --> j2 : Retoma
actions --> j3 : em segundo plano

note bottom of jobs
Cada trabalho conserva
seu estado exato
em os suspend/resume
end note
@enduml

Resumo dos comandos dos trabalhos

pedido efeito Exemplo

Ctrl+Z

Suspende o processo em primeiro plano

Envio de`SIGTSTP`

fg

Retoma o job padrão em primeiro plano

fg ou fg %1

fg %n

Retoma o job n em primeiro plano

fg %2

bg

Retome o trabalho padrão em segundo plano

bg ou bg %1

bg %n

Retoma o job n em segundo plano

bg %2

jobs

Lista os jobs do shell corrente

jobs -l(com PID)

kill %n

Termine o job n

kill %2

Diferenças entre fg, bg, Ctrl+Z, Ctrl+C e &

Muitos desenvolvedores confundem esses mecanismos. Eis um esclarecimento decisivo.

@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif

rectangle "**Ctrl+Z**\nSIGTSTP" as ctrlz #LightYellow {
  card "Suspende o processo\n(ele ainda está na memória)\nUtilizável com fg/bg" as c1
}

rectangle "**Ctrl+C**\nSIGINT" as ctrlc #LightCoral {
  card "Mata o processo\n(sem possibilidade de retorno)\nPerda de todo o estado" as c2
}

rectangle "** & \n(fundo" as ampersand #LightGreen {
  card "Inicie diretamente\nem segundo plano\nTerminal livre immédiatement" as c3
}

rectangle "**fg**\n(primeiro plano)" as fg_ #SkyBlue {
  card "Traga um job\nsuspenso em primeiro plano\nTerminal ocupado" as c4
}

rectangle "**bg**\n(background)" as bg_ #Lavender {
  card "Retoma um trabalho\nsuspenso em segundo plano\nTerminal libre" as c5
}

ctrlz -right-> fg_ : fg para retomar
ctrlz -right-> bg_ : bg para continuar\nem segundo plano
ctrlc -right-> c2 : Sem retorno\nprocesso morto

note bottom of ctrlz
O processo está **parado** mas **não morto**
end note

note bottom of ampersand
Alternativa: iniciar
./gradlew construir &
diretamente em segundo plano
end note
@enduml
Atalho/Comando Sinal Efeito Voltar atrás?

Ctrl+Z

SIGTSTP

Suspender o processo (congelado, na memória)

Sim:`fg` ou bg

Ctrl+C

SIGINT

Mate o processo (terminado)

Não : processo destruído

Ctrl+\

SIGQUIT

Mate o processo + core dump

Não : processo destruído

commande &

—

Execute em segundo plano diretamente

`fg`para trazê-lo de volta para o primeiro plano

Ctrl+Z`énão destrutivo. O processo está congelado tal como está, na memória. Você pode retomar imediatamente com`fg. É um pausa, não um parada.

O ciclo de vida completo de um trabalho

@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif

title Ciclo de vida completo de um trabalho shell
state "Não existente" as none
state "Em primeiro plano\n(ocupa o terminal)" as foreground #LightCoral
state "Suspenso\n(Ctrl+Z / SIGTSTP)" as suspended #LightYellow
state "Em segundo plano\n(bg ou &)" as background #LightGreen
state "Concluído\n(exit code 0)" as done #LightGray

[*] --> none
none --> foreground : Digite um comando
foreground --> suspended : Ctrl+Z
foreground --> done : fim normal

suspended --> foreground : fg
suspended --> background : bg
suspended --> done : kill %n

background --> foreground : fg
background --> done : fim normal\n(em segundo plano)
background --> suspended : Ctrl+Z\n(se fg então Ctrl+Z)

done --> [*]

note right of suspended
O estado suspenso é
o pivô de todo o
sistema de jobs
end note
@enduml

Dicas avançadas

fg com o trabalho mais recente e a alternância

fg`sem argumento traz o trabalho marcado+(o mais recente). Mas você também pode usar%-`pour direcionar o trabalho anterior :

$ 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)

matar um trabalho corretamente

$ 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 : desanexar do shell

disown`Remover um trabalho da tabela de trabalhos do shell. O processo continua em execução, mas não pode mais ser trazido de volta com`fg:

$ ./gradlew build &
[1] 12345

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

Combinar com nohup para os processos longos

Para que um processo sobreviva ao encerramento do 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 "Traga o trabalho\nmais recente (+)" as f1
  card "Selecione um trabalho\nespecífico : fg %n" as f2
  card "Altere : fg %-" as f3
}

rectangle "kill %n" as kill_ #LightCoral {
  card "SIGTERM (limpo)" as k1
  card "SIGKILL -9 (força)" as k2
}

rectangle "disown" as disown #Lavender {
  card "Desanexe do shell\n(survit à la déconnexion)" as d1
}

rectangle "nohup + &" as nohup_ #LightGreen {
  card "Processo imune\ncontra SIGHUP\n(survit à la fermeture\ndu terminal)" as n1
}

fg_ -right-> kill_ : se precisa\nterminar
kill_ -right-> disown_ : ou desanexar
nohup_ -left-> fg_ : fg traz ao primeiro plano

note bottom of disown_
Após disown, fg não pode mais retomar o trabalho
end note
@enduml

Armadilhas e erros comuns

Confundir Ctrl+Z e Ctrl+C

Este é o erro mais frequente.`Ctrl+C`mata o processo.`Ctrl+Z`o suspende. Se você pressiona em`Ctrl+C`por reflexo, todo o estado é perdido — arquivos abertos, sessões LLM, compilações em andamento.

@startuml
skinparam backgroundColor #FEFEFE

rectangle "Ctrl+Z (SIGTSTP)" as z #LightGreen {
  card "Processo congelado na memória" as z1
  card "fg o traz de volta intacto" as z2
  card "Nenhuma perda" as z3
}

rectangle "Ctrl+C (SIGINT)" as c #LightCoral {
  card "Processo encerrado" as c1
  card "Estado perdido definitivamente" as c2
  card "Nenhum retorno possível" as c3
}

note bottom of z
Pause → Retomar
end note

note bottom of c
Kill → Perdido
end note
@enduml

Os trabalhos são locais ao shell

jobs, fg et `bg`funcionam apenas no shell que iniciou os processos. Se você abre um novo terminal, você não verá os trabalhos do outro.

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

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

Para ver os processos system-wide, utiliza`ps` ou htop:

ps aux | grep vim

Os jobs não sobrevivem ao fechamento do shell

Se você fechar o terminal, todos os trabalhos suspensos ou em segundo plano são destruídos. Para que um processo sobreviva, utilize`nohup`+disown:

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

Os processos interativos e fg

Alguns programas interativos não se portam bem depois de um`Ctrl+Z`+fg. É raro mas acontece :

  • Alguns programas que utilizam sinais (vim, htop, less lidam bem`Ctrl+Z`)

  • Os programas de rede longos (ssh, opencode) geralmente recuperam corretamente

  • Os programas que modificam o terminal (ncurses) podem, às vezes, deixar o terminal num estado inesperado

Em caso de terminal corrompido depois de um`fg`, digite :

reset

Diagrama resumo

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

title "fg — A dica que muda tudo"

rectangle "**Problema**" as problem #LightCoral {
  card "Terminal travado\npor um processo" as p1
  card "Abrir um novo\nterminal = solução pesada" as p2
}

rectangle "**Solução**" as solution #LightGreen {
  card "Ctrl+Z → suspende" as s1
  card "fg → retoma" as s2
  card "bg → em segundo plano" as s3
  card "jobs → ver os suspensos" as s4
}

rectangle "**Vantagens**" as advantages #SkyBlue {
  card "Sem perda de contexto" as a1
  card "Sem reinicialização" as a2
  card "Um único terminal basta" as a3
}

problem -right-> solution : "A solução nativa\nUnix há 40 anos"
solution -right-> advantages : "Resultado"

note bottom of advantages
fg não é uma dica obscura
É um mecanismo fundamental
do shell Unix
end note
@enduml

Por que fg é subestimado

A maioria dos desenvolvedores modernos descobre`fg`tarde, ou mesmo nunca. Algumas razões:

  1. Os IDEs escondem o terminal: VS Code oferece um terminal integrado, mas as abas múltiplas tornam`fg`menos necessário em aparência

  2. As ferramentas gráficas substituem a CLI: os gerenciadores de arquivos, os monitores de sistema, os clientes Git gráficos

  3. tmux e screen: esses multiplexadores resolvem o mesmo problema de maneira diferente, mas`fg`é mais simples e sempre disponível

Contudo,`fg`é:

  1. Universal: presente em todos os shells POSIX (bash, zsh, fish…​)

  2. Sem dependência: não é necessário instalar o tmux ou o screen

  3. Instantâneo: duas pancadas (Ctrl+Z`então`fg) para suspender e retomar

  4. Preservador de contexto: o estado do processo está íntegro

@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif

rectangle "Por que fg é\nsubestimado?" as why #LightCoral {
  card "IDE com terminal\nmulti-abas" as w1
  card "Ferramentas gráficas\nque substituem a CLI" as w2
  card "tmux/screen\nresolvem o problema\nde outra forma" as w3
}

rectangle "Por que fg é\nsuperior?" as superior #LightGreen {
  card "Universal: bash,\nzsh, fish, sh..." as s1
  card "Sem dependência:\nsem instalação" as s2
  card "Duas teclas:\nCtrl+Z e depois fg" as s3
  card "Contexto preservado:\nzero perda de estado" as s4
}

why -right-> superior : "A simplicidade\nsempre vence"
@enduml

Memória técnica: os sinais

Para os curiosos, aqui estão os sinais envolvidos:

sinal Nome Enviado por efeito

2

SIGINT

Ctrl+C

Interrupção — termina o processo

3

SIGQUIT

Ctrl+\

Encerra com core dump

18

SIGTSTP

Ctrl+Z

Interativo — suspende o processo

19

SIGCONT

fg/bg

Continua — retoma um processo suspenso

15

SIGTERM

kill %n

terminação limpa

9

SIGKILL

kill -9 %n

Terminação forçada (impossível de interceptar)

O fluxo completo :

@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif

participant "Utilizador" as user
participant "Shell" as shell
participant "Processo" as proc

user -> shell : Ctrl+Z
shell -> proc : SIGTSTP (signal 18)
note right of proc : Processo congelado\nna memória

user -> shell : fg
shell -> proc : SIGCONT (signal 19)
note right of proc : Processo retomado\nem primeiro plano

user -> shell : bg
shell -> proc : SIGCONT (signal 19)
note right of proc : Processo retomado\nem segundo plano

user -> shell : Ctrl+C
shell -> proc : SIGINT (signal 2)
note right of proc : Processo concluído\nestado perdido
@enduml

Conclusão

`fg`não é uma dica obscura — é um mecanismo fundamental do shell Unix, disponível desde os anos 70. O fato de a maioria dos desenvolvedores ignorá-lo é um sintoma da nossa época: as IDEs e os multiplexadores nos afastaram das bases.

Mas quando você está em um servidor SSH, em um terminal mínimo ou simplesmente usando`opencode`e o LLM está em pleno trabalho —Ctrl+Z, fg, bg, `jobs`são seus melhores amigos.

_ Ctrl+Z suspende, fg retoma. É o pausa-play do terminal. _

Artigos relacionados