tiempo de lectura : 10 minutes

Lanzas`./gradlew build`, el terminal está bloqueado durante 3 minutos, y necesitas hacer otra cosa mientras esperas? No es necesario abrir una nueva pestaña.`Ctrl+Z`suspende el proceso,`fg`Lo lleva exactamente donde lo dejaste. Es el tip de Unix más subestimado del día a día de un desarrollador.

tic

[]

El problema: un terminal ocupado

Todo desarrollador ha conhecido esta situación:

$ ./gradlew build
> Building...

El terminal está bloqueado. No puedes escribir ningún comando. Y necesitas consultar un archivo, lanzar un`git status`, o comprobar un puerto ocupado.

Failed to generate image: PlantUML preprocessing failed: [From <input> (line 6) ]

@startuml
skinparam backgroundColor #FEFEFE

actor Développeur as dev
participant "Terminal" as term
participant "Gradle
^^^^^
 Syntax Error? (Assumed diagram type: sequence)

@startuml
skinparam backgroundColor #FEFEFE

actor Développeur as dev
participant "Terminal" as term
participant "Gradle
( proceso )" as gradle

dev -> term : ./gradlew build
term -> gradle : Lance le build
gradle -> term : Occupe le terminal\n(pas de prompt)
dev -> term : veut taper une commande
term --> dev : ❌ Terminal bloqué

note right of dev
  Le développeur est
  paralysé pendant
  toute la durée du build
end note
@enduml

La mayoría de los desarrolladores abren una nueva terminal. Pero existe una solución más elegante, más rápida y nativa: la gestión de los trabajos.

Los tres comandos indispensables

Ctrl+Z — Suspender (SIGTSTP)

`Ctrl+Z`envía la señal`SIGTSTP`al proceso en primer plano. El proceso essuspendido— no matado — y el terminal recupera su prompt.

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

El número entre corchetes`[1]`es elnúmero de trabajo. Le `+`indica el trabajo por defecto.

fg — Traer al primer plano

`fg`Lleva el trabajo suspendido al primer plano. El proceso continúa exactamente donde se había detenido.

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

Con un número de trabajo específico :

$ fg %2

bg — Reanudar en segundo plano

`bg`reanuda el proceso sin bloquear el terminal. Ideal para las builds largas.

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

Le `&`al final significa que el proceso se ejecuta en segundo plano.

jobs — Listar los trabajos del shell

$ jobs
[1]-  Stoppé    vim README.md
[2]+  En cours   ./gradlew build
Failed to generate image: PlantUML preprocessing failed: [From <input> (line 6) ]

@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif

state "Primer plano\n(terminal bloqueado)" as fg #LightCoral
state "Suspendido
^^^^^
 Syntax Error? (Assumed diagram type: state)

@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif

state "Primer plano\n(terminal bloqueado)" as fg #LightCoral
state "Suspendido
(Ctrl+Z)" as stopped #LightYellow
state "Fondo\n(bg)" as bg_ #LightGreen

[*] --> fg : Lance une commande
fg --> stopped : Ctrl+Z\n(SIGTSTP)
stopped --> fg : fg\nreprend en avant-plan
stopped --> bg_ : bg\nreprend en arrière-plan
bg_ --> fg : fg\nramène en avant-plan
fg --> [*] : Commande terminée

note right of stopped
  Le processus est figé
  mais _pas_ tué
end note

note left of bg_
  Le terminal est libre
  le build continue
end note
@enduml

Escenario 1: El build largo

Es el caso de uso más común. Ejecutas una compilación de Gradle, el terminal queda bloqueado y necesitas hacer otra cosa.

Failed to generate image: PlantUML preprocessing failed: [From <input> (line 8) ]

@startuml
skinparam backgroundColor #FEFEFE
autonumber

actor Développeur as dev
participant "Terminal" as term
participant "Gradle" as gradle
database "Estado
^^^^^
 Syntax Error? (Assumed diagram type: sequence)

@startuml
skinparam backgroundColor #FEFEFE
autonumber

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

dev -> term : ./gradlew build
term -> gradle : Lance le build
gradle -> state : En cours (FG)
dev -> term : Ctrl+Z
term -> state : Suspendu (job [1])
term --> dev : Prompt libre $

dev -> term : git status
term --> dev : working tree clean

dev -> term : fg
term -> state : Reprend (FG)
gradle --> dev : Build réussi ✓

note right of state
  Le processus Gradle
  n'a jamais été tué
  Il reprend exactement
  où il s'était arrêté
end note
@enduml

Paso a paso

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

Escenario 2: El editor olvidado

Estás en`vim`, necesitas consultar otro archivo sin salir`vim`.

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

Pero es pesado. Con`Ctrl+Z`, es inmediato :

# 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
vim suspend

Escenario 3: El proceso opencode suspendido

Es el escenario que motivó este artículo. Usted utiliza`opencode`(herramienta CLI para controlar un LLM en tu código), y el proceso está suspendido.

Failed to generate image: PlantUML preprocessing failed: [From <input> (line 7) ]

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

actor Développeur as dev
participant "opencode
^^^^^
 Syntax Error? (Assumed diagram type: sequence)

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

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

dev -> agent : Session active
agent -> agent : Tour de parole\ndu LLM en cours

note over agent
  Ctrl+Z arrive
  en plein milieu
  d'une analyse de code
end note

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

note right of agent
  Aucune perte de contexte
  Le LLM reprend sa réponse
  exactement où il en était
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é

La ventaja es doble:

  1. Sin pérdida de contexto: la sesión LLM, el historial de conversación, los archivos abiertos — todo está preservado

  2. Sin reinicio: no es necesario volver a ejecutar opencode, recargar el contexto, volver a explicar el problema al LLM

Escenario 4: Gestionar varios trabajos simultáneamente

fg et `bg`aceptan un número de trabajo para dirigirse a un proceso específico cuando varios están suspendidos.

$ 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
multiple jobs

Resumen de los comandos de trabajos

Orden Efecto Ejemplo

Ctrl+Z

Suspenda el proceso en primer plano

Envío de`SIGTSTP`

fg

Reanuda el trabajo predeterminado en primer plano

fg ou fg %1

fg %n

Reanuda el trabajo n en primer plano

fg %2

bg

Reanuda el trabajo predeterminado en segundo plano

bg ou bg %1

bg %n

Reanuda el trabajo n en segundo plano

bg %2

jobs

Enumera los trabajos del shell actual

jobs -l(con PID)

kill %n

Termina el trabajo n

kill %2

Diferencias entre fg, bg, Ctrl+Z, Ctrl+C y &

Muchos desarrolladores confunden estos mecanismos. Aquí tienes una aclaración decisiva.

Failed to generate image: PlantUML preprocessing failed: [From <input> (line 10) ]

@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif

rectangle "**Ctrl+Z**\nSIGTSTP" as ctrlz #LightYellow {
  card "Suspende el proceso\n(todavía está en memoria)\nUtilizable con fg/bg" as c1
}

rectangle "**Ctrl+C**
SIGINT" as ctrlc #LightCoral {
^^^^^
 Syntax Error? (Assumed diagram type: activity)

@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif

rectangle "**Ctrl+Z**\nSIGTSTP" as ctrlz #LightYellow {
  card "Suspende el proceso\n(todavía está en memoria)\nUtilizable con fg/bg" as c1
}

rectangle "**Ctrl+C**
SIGINT" as ctrlc #LightCoral {
  card "Finaliza el proceso
(sin posibilidad de retorno)
Pérdida de todo el estado" as c2
}

rectangle "** & \n(fondo)" as ampersand #LightGreen {
  card "Inicia directamente
en segundo plano
Terminal libre inmediatamente" as c3
}

rectangle "**fg**\n(primer plano)" as fg_ #SkyBlue {
  card "Trae un trabajo
suspendido en primer plano
Terminal ocupado" as c4
}

rectangle "**bg**
(fondo)" as bg_ #Lavender {
  card "Reanuda un trabajo
suspendido en segundo plano
Terminal libre" as c5
}

ctrlz -right-> fg_ : fg pour reprendre
ctrlz -right-> bg_ : bg pour continuer\nen arrière-plan
ctrlc -right-> c2 : Pas de retour\nprocessus tué

note bottom of ctrlz
  Le processus est **figé**
  mais **pas tué**
end note

note bottom of ampersand
  Alternative : lancer
  ./gradlew build &
  directement en arrière-plan
end note
@enduml
Atajo/Comando Señal Efecto ¿Volver atrás?

Ctrl+Z

SIGTSTP

Suspender el proceso (congelado, en memoria)

Sí :`fg` ou bg

Ctrl+C

SIGINT

Mata el proceso (terminado)

No: proceso destruido

Ctrl+\

SIGQUIT

Mata el proceso + core dump

No : proceso destruido

commande &

—

Lanza en segundo plano directamente

`fg`para llevarlo al primer plano

Ctrl+Z`esno destructivo. El proceso está congelado tal cual, en memoria. Puede continuar inmediatamente con`fg. Es un pause, no un stop.

El ciclo de vida completo de un trabajo

Failed to generate image: PlantUML preprocessing failed: [From <input> (line 8) ]

@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif

title Ciclo de vida completo de un trabajo shell
state "Inexistente" as none
state "Primer plano\n(ocupa la terminal)" as foreground #LightCoral
state "Suspendido
^^^^^
 Syntax Error? (Assumed diagram type: state)

@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif

title Ciclo de vida completo de un trabajo shell
state "Inexistente" as none
state "Primer plano\n(ocupa la terminal)" as foreground #LightCoral
state "Suspendido
(Ctrl+Z / SIGTSTP)" as suspended #LightYellow
state "Fondo
(bg o &)" as background #LightGreen
state "Terminado
(exit code 0)" as done #LightGray

[*] --> none
none --> foreground : tape une commande
foreground --> suspended : Ctrl+Z
foreground --> done : fin normale

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

background --> foreground : fg
background --> done : fin normale\n(en arrière-plan)
background --> suspended : Ctrl+Z\n(si fg puis Ctrl+Z)

done --> [*]

note right of suspended
  L'état suspendu est
  le pivot de tout le
  système de jobs
end note
@enduml

Consejos avanzados

fg con el trabajo más reciente y la alternancia

fg`Sin argumento trae el trabajo marcado.+(el más reciente). Pero también puedes usar%-`Para apuntar al trabajo 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 un trabajo de forma limpia

$ 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 : desanclar del shell

disown`Elimina un trabajo de la tabla de trabajos del shell. El proceso sigue ejecutándose pero ya no puede ser recuperado con`fg :

$ ./gradlew build &
[1] 12345

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

Combinar con nohup para procesos largos

Para que un proceso sobreviva al cierre del terminal :

$ nohup ./gradlew build &
[1] 12345
$ disown %1
# Le build continue même si vous fermez le terminal
Failed to generate image: PlantUML preprocessing failed: [From <input> (line 8) ]

@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif

rectangle "fg" as fg_ #SkyBlue {
  card "Trae el trabajo\nel más reciente (+)" as f1
  card "Apunta a un trabajo
específico : fg %n" as f2
^^^^^
 Syntax Error? (Assumed diagram type: activity)

@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif

rectangle "fg" as fg_ #SkyBlue {
  card "Trae el trabajo\nel más reciente (+)" as f1
  card "Apunta a un trabajo
específico : fg %n" as f2
  card "Alterna : fg %-" as f3
}

rectangle "matar %n" as kill_ #LightCoral {
  card "SIGTERM (limpio)" as k1
  card "SIGKILL -9 (fuerza)" as k2
}

rectangle "renegar" as disown #Lavender {
  card "Desvincular del shell
(sobrevive a la desconexión)" as d1
}

rectangle "nohup + &" as nohup_ #LightGreen {
  card "Proceso inmunizado
contra SIGHUP
(sobrevive al cierre
del terminal)" as n1
}

fg_ -right-> kill_ : si faut\nterminer
kill_ -right-> disown_ : ou détacher
nohup_ -left-> fg_ : fg ramène en\navant-plan

note bottom of disown_
  Après disown, fg ne peut
  plus ramener le job
end note
@enduml

Trampas y errores comunes

Confundir Ctrl+Z y Ctrl+C

Es el error más frecuente.`Ctrl+C`mata el proceso.`Ctrl+Z`lo suspende. Si lo presiona`Ctrl+C`por reflejo, todo el estado se pierde — archivos abiertos, sesiones LLM, builds en curso.

ctrlz vs ctrlc

Los trabajos son locales al shell

jobs, fg et `bg`solo funcionan en el shell que lanzó los procesos. Si abre un nuevo terminal, no verá los trabajos del otro.

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

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

Para ver los procesos system-wide, utilice`ps` ou htop:

ps aux | grep vim

Los trabajos no sobreviven al cierre del shell

Si cierras el terminal, todos los trabajos suspendidos o en segundo plano son destruidos. Para que un proceso sobreviva, utilice`nohup`+disown:

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

Los procesos interactivos y fg

Algunos programas interactivos no funcionan bien después de un`Ctrl+Z`+fg. Es raro pero ocurre :

  • Algunos programas que utilizan señales (vim, htop, less manejan bien`Ctrl+Z`)

  • Los programas de red de larga duración (ssh, opencode) generalmente se recuperan correctamente

  • Los programas que modifican el terminal (ncurses) pueden a veces dejar el terminal en un estado inesperado

En caso de terminal corrupto después de un`fg`, escriba :

reset

Diagrama resumido

Failed to generate image: PlantUML preprocessing failed: [From <input> (line 10) ]

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

title "fg — El consejo que cambia todo"

rectangle "Problema" as problem #LightCoral {
  card "Terminal bloqueado
por un proceso" as p1
^^^^^
 Syntax Error? (Assumed diagram type: activity)

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

title "fg — El consejo que cambia todo"

rectangle "Problema" as problem #LightCoral {
  card "Terminal bloqueado
por un proceso" as p1
  card "Abrir un nuevo
terminal = solución pesada" as p2
}

rectangle "**Solución**" as solution #LightGreen {
  card "Ctrl+Z → suspender" as s1
  card "fg → retoma" as s2
  card "bg → en segundo plano" as s3
  card "jobs → ver los suspendidos" as s4
}

rectangle "Ventajas" as advantages #SkyBlue {
  card "Sin pérdida de contexto" as a1
  card "No reinicio" as a2
  card "Un solo terminal basta" as a3
}

problem -right-> solution : "La solución nativa\nUnix desde hace 40 años"
solution -right-> advantages : "Resultado"

note bottom of advantages
  fg n'est pas un tips obscur
  C'est un mécanisme fondamental
  du shell Unix
end note
@enduml

¿Por qué fg está subestimado

La mayoría de los desarrolladores modernos descubren`fg`tarde, o incluso nunca. Algunas razones :

  1. Los IDE ocultan la terminal: VS Code ofrece un terminal integrado, pero las pestañas múltiples hacen`fg`menos necesario en apariencia

  2. Las herramientas gráficas reemplazan a la CLI: los gestores de archivos, los monitores del sistema, los clientes gráficos de Git

  3. tmux y screen: estos multiplexores resuelven el mismo problema de manera diferente, pero`fg`es más simple y siempre disponible

Pero,`fg`es :

  1. universal: presente en todos los shells POSIX (bash, zsh, fish…​)

  2. Sin dependencia: no es necesario instalar tmux o screen

  3. Instantáneo: dos golpes (Ctrl+Z`luego`fg) para suspender y reanudar

  4. **Preservador de contexto

Wait that’s wrong: Should be "Preservador de contexto". Let’s output exactly that.

</think>

Preservador de contexto**: el estado del proceso está intacto

Failed to generate image: PlantUML preprocessing failed: [From <input> (line 6) ]

@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif

rectangle "¿Por qué fg está
subestimado?" as why #LightCoral {
^^^^^
 Syntax Error? (Assumed diagram type: activity)

@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontName sans-serif

rectangle "¿Por qué fg está
subestimado?" as why #LightCoral {
  card "IDE con terminal\nmultipestañas" as w1
  card "Herramientas gráficas
que sustituyen la CLI" as w2
  card "tmux/screen
resuelven el problema
de otra manera" as w3
}

rectangle "¿Por qué fg es
superior?" as superior #LightGreen {
  card "Universal: bash,\nzsh, fish, sh..." as s1
  card "Sin dependencia:
Sin instalación" as s2
  card "Dos pulsaciones:\nCtrl+Z luego fg" as s3
  card "Contexto preservado:\ncero pérdida de estado" as s4
}

why -right-> superior : "La simplicidad
siempre gana"
@enduml

Memoria técnica: las señales

Para los curiosos, aquí están las señales implicadas:

señal Nombre Enviado por Efecto

2

SIGINT

Ctrl+C

Interrupción — termina el proceso

3

SIGQUIT

Ctrl+\

Sale con volcado de núcleo

18

SIGTSTP

Ctrl+Z

Parada interactiva — suspende el proceso

19

SIGCONT

fg/bg

Continúa — reanuda un proceso suspendido

15

SIGTERM

kill %n

terminación limpia

9

SIGKILL

kill -9 %n

Finalización forzada (imposible de interceptar)

El flujo completo:

signal flow

Conclusión

`fg`No es un consejo oscuro — es un mecanismo fundamental del shell Unix, disponible desde los años 70. El hecho de que la mayoría de los desarrolladores lo ignoren es un síntoma de nuestra época: los IDE y los multiplexores nos han alejado de lo básico.

Pero cuando estás en un servidor SSH, en una terminal mínima, o simplemente estás utilizando`opencode`y que el LLM está en pleno trabajo —Ctrl+Z, fg, bg, `jobs`son tus mejores amigos.

_ Ctrl+Z suspend, fg reanuda. Es la pausa-reproducción del terminal. _

Articles connexes