1. Primer plano y segundo plano
Desde el primer tema, cada comando siguió el mismo ciclo: se escribe, se pulsa Intro, la shell lo ejecuta y espera a que termine antes de mostrar otra vez el indicador. Con ls la espera no se nota. Con un comando que tarda, la terminal queda tomada. Ese modo de ejecución tiene nombre, y tiene una alternativa.
Un comando en primer plano (foreground) ocupa la terminal: recibe lo que se teclea, y la shell espera a que termine. Un comando en segundo plano (background) se ejecuta sin ocupar la terminal: la shell muestra el indicador enseguida y se puede seguir trabajando.
| Primer plano | Segundo plano | |
|---|---|---|
| La shell | Espera a que termine | Devuelve el indicador de inmediato |
| El teclado | Lo recibe el comando | Lo recibe la shell |
| Ctrl+C y Ctrl+Z | Lo afectan | No le llegan |
| Cuántos puede haber | Uno por terminal | Los que se quiera |
| Sirve para | Lo breve y lo interactivo | Lo que tarda y no necesita atención |
En el tema anterior resolvimos el problema abriendo una segunda terminal para el sleep. Es una solución legítima, y a veces la mejor. Pero no siempre hay otra ventana a mano —en una conexión remota, en una consola sin escritorio—, y la shell tiene sus propios recursos para manejar varios programas desde un solo lugar. Se los conoce como control de trabajos.
Usaremos sleep como programa de prueba: es inofensivo, no escribe nada y dura lo que se le pida. Todo lo que hagamos con él vale para una copia, una descarga o una compilación.
2. &: lanzar en segundo plano
Para ejecutar un comando en segundo plano se agrega un signo & al final de la línea:
$ cd ~/practica
$ sleep 3000 &
[1] 7702
$ █
El indicador volvió al instante, y el sleep está en marcha. La shell respondió con dos números, que identifican lo mismo de dos maneras:
[1] 7702
│ │
│ └─ el PID: el número del proceso para todo el sistema
└─ el número de trabajo: el que le da esta shell
El proceso existe y se ve con las herramientas del tema 32, conectado a la misma terminal que la shell, que es su padre:
$ ps
PID TTY TIME CMD
3187 pts/0 00:00:00 bash
7702 pts/0 00:00:00 sleep
7704 pts/0 00:00:00 ps
$ echo $! # el PID del último comando lanzado en segundo plano
7702
El signo & es un terminador de comando, como el punto y coma del tema 25, con otra consigna: «ejecuta esto y no esperes». No hay que confundirlo con &&, que encadena dos comandos con una condición, ni con el & de 2>&1, que pertenece a las redirecciones.
Cuando un trabajo en segundo plano termina, la shell lo avisa. No interrumpe lo que se esté escribiendo: espera al siguiente indicador, y entonces muestra una línea. Se ve con un segundo trabajo, breve:
$ sleep 5 &
[2] 7705
$ # pasados cinco segundos, pulsamos Intro
[2]+ Done sleep 5
El primer trabajo, de cincuenta minutos, sigue en marcha. Elegimos duraciones largas para que no se nos terminen a mitad del tema.
3. jobs: los trabajos de la shell
Un trabajo (job) es un comando, o una tubería entera, que la shell lanzó y tiene bajo su control. Cada shell numera sus trabajos por separado, a partir de 1.
Lanzamos otro trabajo largo y pedimos la lista con jobs:
$ sleep 4000 &
[2] 7707
$ jobs
[1]- Running sleep 3000 &
[2]+ Running sleep 4000 &
$ jobs -l # con el PID
[1]- 7702 Running sleep 3000 &
[2]+ 7707 Running sleep 4000 &
| Parte | Significa |
|---|---|
[2] | El número de trabajo. |
+ | El trabajo actual: el último que se lanzó o se suspendió. Sobre él actúan fg y bg cuando no se les dice otra cosa. |
- | El trabajo anterior al actual. |
Running, Stopped, Done | El estado: en ejecución, suspendido, terminado. |
sleep 4000 & | La línea de comandos. |
Trabajo y proceso no son la misma cosa, aunque aquí coincidan, y la diferencia explica por qué hay dos numeraciones:
| Proceso | Trabajo | |
|---|---|---|
| Lo administra | El kernel | La shell |
| Se identifica con | El PID: 7702 | El número de trabajo: %1 |
| El número vale | En todo el sistema | Solo en la shell que lo lanzó |
| Se lista con | ps | jobs |
| Contiene | Un programa en ejecución | Uno o varios procesos: una tubería completa es un solo trabajo |
La última fila es la razón de ser del concepto. sort archivo | uniq -c | less son tres procesos, y para quien lo escribió es una sola tarea: la shell los agrupa en un trabajo, y los suspende, reanuda o termina juntos. La tercera fila tiene una consecuencia práctica: jobs en otra terminal no muestra nada, porque esa es otra shell, con su propia lista.
Para referirse a un trabajo se usa el signo %:
| Se escribe | Designa |
|---|---|
%1, %2 | El trabajo con ese número. |
%+ o %% | El trabajo actual, el marcado con +. |
%- | El anterior, el marcado con -. |
%sleep | El trabajo cuyo comando empieza con ese texto. |
4. Ctrl+Z, bg y fg
El signo & sirve cuando se sabe de antemano que el comando va a tardar. Lo habitual es darse cuenta después: el comando ya está en marcha, en primer plano, y la terminal está tomada. Para ese caso hay tres herramientas que trabajan juntas.
| Herramienta | Efecto |
|---|---|
| Ctrl+Z | Suspende el comando que está en primer plano: lo deja en pausa, y devuelve el indicador. |
bg | Reanuda un trabajo suspendido, en segundo plano (background). |
fg | Trae un trabajo al primer plano (foreground), esté suspendido o en ejecución. |
Las probamos con los dos trabajos que tenemos. Primero traemos uno al frente:
$ fg %1
sleep 3000
█ # la terminal queda ocupada; pulsamos Ctrl+Z
^Z
[1]+ Stopped sleep 3000
$ jobs
[1]+ Stopped sleep 3000
[2]- Running sleep 4000 &
fg escribió el comando, para recordar de qué se trata, y la terminal quedó como si lo hubiéramos lanzado de la manera común. Con Ctrl+Z, que la terminal representa como ^Z, el trabajo quedó Stopped. El término engaña: no terminó. Está congelado, con todo su estado en memoria, sin avanzar. Ahora lo reanudamos, pero al fondo:
$ bg %1
[1]+ sleep 3000 &
$ jobs
[1]+ Running sleep 3000 &
[2]- Running sleep 4000 &
La secuencia Ctrl+Z y luego bg es la respuesta a «lancé algo largo y me olvidé del &»: el resultado es idéntico a haberlo puesto. Como el trabajo recién suspendido es el actual, bg sin argumentos alcanza.
fg trae a cualquiera de vuelta.Con el trabajo otra vez al frente, lo terminamos con Ctrl+C, y queda uno solo:
$ fg # sin argumento: el trabajo actual
sleep 3000
^C
$ jobs
[2]+ Running sleep 4000 &
Aquí está, por fin, la explicación que quedó pendiente en el tema 13. Pulsar Ctrl+Z dentro de vim, de less o de man no los cierra: los suspende, y aparece el indicador con un Stopped encima. El archivo sigue abierto, con los cambios sin guardar. Con fg se vuelve exactamente al lugar donde se estaba. Usado a propósito es un recurso cómodo: suspender el editor, ejecutar dos comandos en la shell y regresar.
El descuido clásico es suspender un editor con Ctrl+Z, olvidarlo, y abrir el mismo archivo otra vez. Quedan dos editores sobre un mismo archivo, uno de ellos invisible. Si al escribir exit la shell responde There are stopped jobs, es eso: hay trabajos en pausa. jobs los muestra, y fg permite volver a cada uno para cerrarlo como corresponde.
5. Lo que un trabajo escribe y lee
Enviar un comando al segundo plano le quita el teclado, y nada más. Sus tres canales estándar, heredados de la shell, siguen apuntando a la terminal. Si el comando escribe algo, lo escribe ahí, en medio de lo que se esté haciendo:
$ sleep 5 && echo "Listo el respaldo" &
[3] 7750
$ Listo el respaldo
█ # el cursor quedó sin indicador; pulsamos Intro
[3]+ Done sleep 5 && echo "Listo el respaldo"
$
El mensaje apareció cinco segundos después, a continuación del indicador que ya estaba dibujado. No se rompió nada: la shell sigue esperando un comando, y basta pulsar Intro para tener un indicador limpio. Con un comando que escribe cientos de líneas, en cambio, la terminal se vuelve inusable. La regla es redirigir:
$ find / -name "*.conf" > /tmp/conf.txt 2> /dev/null &
[3] 7760
$ wc -l /tmp/conf.txt # se puede mirar el resultado mientras se va formando
214 /tmp/conf.txt
$
[3]+ Exit 1 find / -name "*.conf" > /tmp/conf.txt 2> /dev/null
La salida fue a un archivo, los errores a /dev/null, y el & va al final de todo, después de las redirecciones. El aviso de cierre dice Exit 1 en lugar de Done: el comando terminó con código de salida 1, porque find tropezó con directorios que no pudo leer. Done es código cero.
Con la entrada ocurre lo contrario. Un trabajo en segundo plano no puede leer del teclado, que pertenece a quien está en primer plano. Si lo intenta, el kernel lo suspende, y la shell lo informa como Stopped (tty input). Hay que traerlo con fg para responderle. Por eso los programas interactivos —un editor, un comando que pide confirmación— no se lanzan con &.
| Un trabajo en segundo plano… | Qué ocurre | Qué hacer |
|---|---|---|
| Escribe en su salida o en la de errores. | El texto aparece en la terminal, mezclado. | Redirigir: > archivo 2>&1 & |
| Intenta leer del teclado. | Queda suspendido. | No lanzarlo así; o fg. |
| Termina. | La shell lo avisa antes del siguiente indicador. | Nada. |
6. Las señales
Ctrl+C termina un programa; Ctrl+Z lo suspende; bg lo reanuda. Parecen tres mecanismos distintos, y son uno solo.
Una señal es un aviso que se le envía a un proceso para informarle de un acontecimiento o pedirle algo. No lleva datos: es un número, de un repertorio fijo. El proceso lo recibe en cualquier momento, interrumpiendo lo que estuviera haciendo.
Cada señal tiene un número y un nombre que empieza con SIG. Hay unas sesenta; en el uso diario importan ocho:
| Señal | N.º | Pide o avisa | La envía | Efecto habitual |
|---|---|---|---|---|
SIGHUP | 1 | «Se cerró tu terminal.» | El cierre de la ventana o de la conexión. | Termina. |
SIGINT | 2 | «Interrumpe lo que haces.» | Ctrl+C | Termina. |
SIGQUIT | 3 | «Abandona, y deja un registro.» | Ctrl+\ | Termina. |
SIGKILL | 9 | Nada: el kernel lo elimina. | kill -9 | Termina, sin excepción. |
SIGTERM | 15 | «Termina, por favor.» | kill, y el apagado del sistema. | Termina. |
SIGCONT | 18 | «Continúa.» | fg, bg | Reanuda si estaba suspendido. |
SIGSTOP | 19 | Nada: el kernel lo congela. | kill -STOP | Se suspende, sin excepción. |
SIGTSTP | 20 | «Suspéndete.» | Ctrl+Z | Se suspende. |
La columna de la derecha dice «habitual» por una razón que es el centro del tema. Al recibir una señal, un proceso puede hacer una de tres cosas:
| El proceso puede… | Qué significa | Ejemplo |
|---|---|---|
| Dejar la acción por defecto. | Lo que el kernel hace si el programa no previó nada: casi siempre, terminarlo. | sleep ante Ctrl+C. |
| Atender la señal. | El programa tiene preparada una respuesta propia: guardar, cerrar archivos, despedirse. | Un editor que guarda una copia de emergencia antes de salir. |
| Ignorarla. | El programa decidió no hacer caso. | nano y less ante Ctrl+C. |
De ahí el vocabulario cortés de la tabla: una señal es un pedido, y el programa resuelve cómo responder. Hay dos excepciones, las marcadas «sin excepción»: SIGKILL y SIGSTOP no llegan nunca al programa. Las ejecuta el kernel directamente, y no se pueden atender ni ignorar.
Las combinaciones de teclas no tienen nada de mágico: la terminal las convierte en señales para el trabajo que está en primer plano. Eso explica por qué Ctrl+C no afecta a los trabajos del fondo.
| Tecla | Envía | Para |
|---|---|---|
| Ctrl+C | SIGINT | Interrumpir. |
| Ctrl+Z | SIGTSTP | Suspender. |
| Ctrl+\ | SIGQUIT | Terminar cuando Ctrl+C no alcanza. |
| Ctrl+D | Ninguna | No es una señal: marca el fin de la entrada, como vimos en el tema 22. |
La lista completa de señales la da kill -l:
$ kill -l | head -n 4
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
Algunos nombres son viejos conocidos con otra cara. SIGSEGV es el célebre Segmentation fault: la señal que el kernel le envía a un programa que intentó usar memoria que no le pertenece. Y SIGPIPE es la que recibe un comando cuando escribe en una tubería que ya nadie lee: así se detiene sort en sort archivo | head -n 1, cuando head ya tuvo su línea y terminó.
7. kill: enviar una señal
El teclado solo alcanza al trabajo que está en primer plano. Para enviarle una señal a cualquier otro proceso está el comando kill.
kill envía una señal a uno o más procesos, identificados por su PID o por su número de trabajo. Si no se indica cuál, envía SIGTERM.
El nombre es desafortunado: kill no mata, envía señales, y varias de ellas no terminan nada. Empezamos por el uso común, con el trabajo que quedó:
$ kill %2 # por número de trabajo
$
[2]+ Terminated sleep 4000
$ sleep 1000 &
[1] 7771
$ kill 7771 # por PID
$
[1]+ Terminated sleep 1000
La señal se indica con un guion, y se puede escribir de tres maneras equivalentes: por número, por nombre, o por nombre completo.
| Comando | Envía | Para |
|---|---|---|
kill PID | SIGTERM | Pedirle que termine. La forma normal. |
kill -15 PID, kill -TERM PID, kill -SIGTERM PID | SIGTERM | Lo mismo, dicho expresamente. |
kill -INT PID | SIGINT | El equivalente de Ctrl+C, a distancia. |
kill -HUP PID | SIGHUP | En muchos servicios, releer la configuración. |
kill -STOP PID | SIGSTOP | Congelarlo. |
kill -CONT PID | SIGCONT | Reanudarlo. |
kill -9 PID, kill -KILL PID | SIGKILL | Eliminarlo sin consultarle. El último recurso. |
Con STOP y CONT se puede pausar cualquier proceso propio, aunque no sea un trabajo de esta shell ni tenga terminal. Lo comprobamos con la columna de estado del tema 32:
$ sleep 1000 &
[1] 7780
$ kill -STOP 7780
$ ps -o pid,stat,cmd -p 7780
PID STAT CMD
7780 T sleep 1000
$ kill -CONT 7780
$ ps -o pid,stat,cmd -p 7780
PID STAT CMD
7780 S sleep 1000
$ kill -9 7780
$
[1]+ Killed sleep 1000
El estado pasó de S a T, detenido, y de vuelta a S. Es un recurso útil con un programa que está acaparando el procesador o el disco en mal momento: se lo congela, se hace lo urgente, y se lo reanuda, sin perder su trabajo. El aviso final dice Killed y no Terminated: la shell informa con qué señal murió cada trabajo.
Esa información queda también en el código de salida. Un proceso terminado por una señal deja como código 128 más el número de la señal:
Código en $? | Cuenta | El proceso terminó por |
|---|---|---|
130 | 128 + 2 | SIGINT: se pulsó Ctrl+C. |
137 | 128 + 9 | SIGKILL. |
143 | 128 + 15 | SIGTERM. |
Hay un límite, y es el de siempre: solo se pueden enviar señales a los procesos propios. Los de otro usuario, y los del sistema, requieren sudo.
$ kill 1 # el proceso 1 es de root
bash: kill: (1) - Operation not permitted
$ kill 99999
bash: kill: (99999) - No such process
Los mensajes empiezan con bash: porque kill es un comando interno de la shell. Tiene que serlo: solo ella sabe qué proceso es %2.
8. Primero TERM, después KILL
En foros y tutoriales abunda una receta: «para cerrar un programa, kill -9». Funciona siempre, y por eso mismo es una mala costumbre. La diferencia entre las dos señales es la que hay entre pedirle a alguien que se retire y desconectarlo de la corriente.
SIGTERM (15) | SIGKILL (9) | |
|---|---|---|
| Llega al programa | Sí | No: actúa el kernel |
| El programa puede | Guardar, cerrar archivos, borrar temporales, avisar a sus hijos | Nada: desaparece en el acto |
| Puede ser ignorada | Sí | No |
| Riesgo | Ninguno | Archivos a medio escribir, datos sin guardar, bloqueos y temporales abandonados, procesos hijos huérfanos |
Un programa terminado con SIGKILL no tiene oportunidad de ordenar nada. Una base de datos puede quedar con una operación por la mitad; un editor, con el archivo sin guardar; un gestor de paquetes, con un archivo de bloqueo que impedirá ejecutarlo de nuevo. El procedimiento correcto es escalonado:
- Pedir:
kill PID, que envíaSIGTERM. pedir - Esperar unos segundos y comprobar con
ps -p PIDsi sigue ahí. Cerrar ordenadamente puede llevar un momento. esperar - Forzar, solo si no respondió:
kill -9 PID. forzar
La gran mayoría de los programas termina correctamente con un kill común. kill -9 se reserva para el que está realmente colgado: no responde a SIGTERM porque quedó en un bucle o porque decidió ignorarla. Es el mismo orden que sigue el sistema al apagarse: envía SIGTERM a todos los procesos, les da un tiempo, y recién entonces SIGKILL a los que quedan.
9. pkill y killall: por nombre
kill necesita un PID, y averiguarlo es un paso previo. Dos comandos lo evitan, buscando los procesos por su nombre.
pkill es el hermano de pgrep que anunciamos en el tema anterior: selecciona los procesos de la misma manera, con las mismas opciones, y en lugar de listarlos les envía una señal. Esa simetría da un método seguro: primero pgrep, después pkill.
$ sleep 600 & sleep 700 & sleep 800 &
[1] 7801
[2] 7802
[3] 7803
$ pgrep -a sleep # mirar a quiénes alcanzaría
7801 sleep 600
7802 sleep 700
7803 sleep 800
$ pkill -f "sleep 700" # solo el que tiene esa línea de comandos
[2]- Terminated sleep 700
$ pkill sleep # todos los que se llamen sleep
[1]- Terminated sleep 600
[3]+ Terminated sleep 800
| Comando | Envía la señal a |
|---|---|
pkill nombre | Los procesos cuyo nombre contiene ese texto. |
pkill -x nombre | Los que se llaman exactamente así. |
pkill -f texto | Los que tienen ese texto en su línea de comandos. |
pkill -u usuario | Todos los procesos de un usuario. |
pkill -t pts/1 | Los conectados a esa terminal. |
pkill -STOP nombre, pkill -9 nombre | Los mismos, con otra señal. |
killall hace algo parecido, con una diferencia de criterio: exige el nombre exacto del programa, no una parte.
pkill | killall | |
|---|---|---|
| El nombre | Es una expresión regular, y coincide con una parte | Debe ser exacto |
| Opciones propias | -f, -u, -t, -n, -o | -i, pregunta antes de cada uno; -u |
| Si no encuentra ninguno | No dice nada; código de salida 1 | Lo informa: no process found |
pkill sh no termina «la shell»: alcanza a bash, a sshd, a gnome-shell y a todo proceso propio cuyo nombre contenga «sh», empezando quizá por la sesión desde la que se lo escribió. Con sudo delante, el alcance es el sistema entero. Antes de un pkill, el mismo patrón con pgrep -a. Y una advertencia para quien administre otros Unix: en algunos, como Solaris, killall sin más termina todos los procesos.
Para las aplicaciones gráficas hay un recurso más: xkill convierte el puntero en una cruz, y la ventana que se pulse se cierra. Y los monitores del tema 32 traen la función incorporada: en top la tecla k pide un PID y una señal, y en htop F9 ofrece la lista de señales para el proceso seleccionado.
10. Cerrar la terminal: SIGHUP, nohup y disown
Un trabajo en segundo plano no ocupa la terminal, pero sigue perteneciendo a ella. Al cerrar la ventana, o al cortarse una conexión remota, el kernel envía SIGHUP a la shell —el nombre viene de hang up, colgar el teléfono, de cuando las terminales se conectaban por línea telefónica—, y la shell la reenvía a sus trabajos. Todos terminan.
Casi siempre es lo deseable: nadie quiere procesos sueltos de sesiones cerradas. Para la tarea larga que debe sobrevivir a la sesión hay dos recursos, según el momento en que se lo recuerde.
nohup comando & ejecuta el comando de modo que ignore la señal SIGHUP. Si la salida iba a la terminal, la desvía a un archivo llamado nohup.out.
$ nohup sleep 2000 &
[1] 7820
nohup: ignoring input and appending output to 'nohup.out'
$ nohup sleep 2000 > /tmp/tarea.log 2>&1 & # mejor: eligiendo el archivo
[2] 7823
nohup es un comando que lanza otro comando, como sudo. No lo manda al segundo plano: el & hay que ponerlo igual. Se puede cerrar la ventana, abrir otra, y el proceso sigue ahí:
$ jobs # nada: esta es otra shell
$ ps -o pid,ppid,tty,cmd -p $(pgrep -f "sleep 2000")
PID PPID TT CMD
7820 2731 ? sleep 2000
7823 2731 ? sleep 2000
Tres cosas cambiaron respecto del tema 32. El proceso ya no es un trabajo de ninguna shell, y solo se lo alcanza por su PID. Su terminal es ?: la que tenía ya no existe. Y su padre no es una shell: al morir la que lo lanzó quedó huérfano, y lo adoptó un proceso superior. Es el caso que habíamos anunciado.
El segundo recurso es para cuando el trabajo ya está en marcha, sin nohup, y hay que irse:
| Recurso | Se usa | Efecto |
|---|---|---|
nohup comando & | Al lanzar el comando. | El comando ignora SIGHUP; su salida va a un archivo. |
disown %1 | Después, con el trabajo ya en segundo plano. | La shell lo borra de su lista de trabajos, y no le reenviará SIGHUP. |
disown -h %1 | Lo mismo. | Lo protege de SIGHUP pero lo deja en la lista. |
disown no redirige nada: si el trabajo escribe en la terminal y esta se cierra, fallará al escribir. Por eso nohup, con las redirecciones puestas, es el más confiable de los dos.
Ambos son soluciones de emergencia. Para trabajar en serio en una máquina remota se usa un multiplexor de terminal, como tmux o screen: mantienen la sesión entera viva en el servidor, con sus programas y su pantalla, y permiten desconectarse y volver a entrar horas después al mismo lugar. Y para un programa que debe funcionar siempre, la herramienta son los servicios del tema 39.
11. nice y renice: ceder el paso
Hay una intervención más suave que pausar o terminar: dejar que el proceso siga, pero sin estorbar. Cuando varios procesos quieren el procesador a la vez, el kernel lo reparte según la prioridad de cada uno, y esa prioridad se puede ajustar con un número llamado nice, la columna NI de top.
El valor nice de un proceso va de -20, la mayor prioridad, a 19, la menor. El valor normal es 0. Cuanto más alto, más «amable» es el proceso: más cede el procesador a los demás.
$ nice -n 10 sleep 900 & # lanzar con prioridad rebajada
[1] 7840
$ ps -o pid,ni,cmd -p 7840
PID NI CMD
7840 10 sleep 900
$ renice -n 15 -p 7840 # cambiarla a un proceso en marcha
7840 (process ID) old priority 10, new priority 15
$ renice -n 5 -p 7840 # volver a subirla: no se permite
renice: failed to set priority for 7840 (process ID): Permission denied
El último intento muestra la regla: un usuario común solo puede bajar la prioridad de sus procesos, es decir, aumentar su valor nice. Subirla, o usar valores negativos, es privilegio de root. De otro modo, todos se darían prioridad máxima.
El valor nice no limita nada: un proceso con nice 19, solo en la máquina, usa el procesador entero. Actúa únicamente cuando hay competencia. Por eso es el ajuste indicado para una compresión o una conversión de video larga: nice -n 19 tar -czf respaldo.tar.gz ~/Documentos & trabaja a pleno mientras la máquina está libre, y se aparta en cuanto se la usa para otra cosa.
12. timeout y wait
Dos comandos pequeños completan el repertorio. Los dos tienen que ver con el tiempo: uno pone un límite, el otro espera.
timeout ejecuta un comando y, si no terminó al cabo del tiempo indicado, le envía SIGTERM. Es la protección contra el comando que podría quedarse colgado para siempre:
$ timeout 3 sleep 100; echo $? # a los tres segundos lo termina
124
$ timeout 3 sleep 1; echo $? # terminó a tiempo: su propio código
0
El código de salida 124 significa «se agotó el tiempo». La duración admite sufijos, 30s, 5m, 2h; y con -k 10 se le agrega el segundo escalón del procedimiento de la sección 8: si diez segundos después de SIGTERM el comando sigue vivo, SIGKILL.
wait hace lo inverso de &: detiene la shell hasta que terminen los trabajos en segundo plano. Sin argumentos los espera a todos; con un PID o un %n, a ese. Permite lanzar varias tareas a la vez y continuar recién cuando todas estén listas:
$ sleep 3 & sleep 3 & sleep 3 & wait; echo "Las tres terminaron"
[1] 7851
[2] 7852
[3] 7853
[1] Done sleep 3
[2]- Done sleep 3
[3]+ Done sleep 3
Las tres terminaron
El mensaje aparece a los tres segundos, no a los nueve: las tres esperas corrieron al mismo tiempo. Con tres compresiones o tres descargas en lugar de tres sleep, es la manera más simple de aprovechar varios núcleos desde la línea de comandos.
13. Cuando un proceso no termina
A veces se envía la señal y el proceso sigue en la lista. Antes de insistir conviene mirar su estado con ps -o pid,ppid,stat,cmd -p PID, porque cada caso tiene otra explicación:
| Se observa | Qué ocurre | Qué hacer |
|---|---|---|
| No responde a Ctrl+C. | El programa atiende o ignora SIGINT. | Su orden de salida propia (q, :q, Ctrl+X); o Ctrl+Z y luego kill %1. |
No responde a kill. | Ignora SIGTERM, o está colgado. | Esperar unos segundos; después, kill -9. |
Está en estado T y no termina con kill. | Está suspendido: la señal queda pendiente hasta que se reanude. | kill -CONT PID, o fg. |
Está en estado Z, <defunct>. | Es un zombie: ya terminó. No hay nada que matar. | Nada; o terminar a su padre, que es quien debe recogerlo. |
Está en estado D y no responde a kill -9. | Espera una operación del disco o de la red que no se puede interrumpir. | Esperar: termina cuando la operación concluye o falla. |
| Termina, y reaparece con otro PID. | Otro proceso lo vigila y lo relanza: es un servicio. | Detenerlo con systemctl, tema 39. |
Operation not permitted | No es un proceso propio. | sudo kill PID, si corresponde. |
La primera fila tiene un recurso que conviene recordar. Un programa puede ignorar Ctrl+C, pero rara vez ignora Ctrl+Z. Suspenderlo devuelve el indicador, y desde ahí kill %1 lo termina: es la salida de emergencia de casi cualquier programa en el que uno haya quedado atrapado.
La penúltima fila es la más desconcertante la primera vez. Los servicios del sistema están supervisados por systemd, que los reinicia si mueren. Enviarles señales es pelear contra el supervisor; lo correcto es pedirle a él que los detenga.
14. Recetas y errores comunes
| Quiero… | Comando |
|---|---|
| Lanzar algo largo sin ocupar la terminal | comando > salida.log 2>&1 & |
| Mandar al fondo algo que ya está en marcha | Ctrl+Z, y luego bg |
| Ver qué tengo en marcha en esta shell | jobs -l |
| Volver a un programa suspendido | fg, o fg %n |
| Interrumpir el comando en primer plano | Ctrl+C |
| Terminar un trabajo en segundo plano | kill %n |
| Terminar un proceso del que conozco el PID | kill PID |
| Terminar un programa por su nombre | pgrep -a nombre; luego pkill nombre |
| Forzar a uno que no responde | kill -9 PID |
| Pausar un proceso, y reanudarlo después | kill -STOP PID; kill -CONT PID |
| Que una tarea siga al cerrar la terminal | nohup comando > salida.log 2>&1 & |
| Lo mismo, con el trabajo ya lanzado | disown %n |
| Que una tarea pesada no moleste | nice -n 19 comando & |
| Limitar cuánto puede durar un comando | timeout 60 comando |
| Seguir el avance de una tarea del fondo | tail -f salida.log |
| Síntoma | Causa | Solución |
|---|---|---|
| Aparece texto en medio de lo que escribo. | Un trabajo en segundo plano escribe en la terminal. | Pulsar Intro; la próxima vez, redirigir su salida. |
Stopped al lanzar algo con &. | El comando quiere leer del teclado. | fg; no lanzar con & programas interactivos. |
| Pulsé Ctrl+Z y el editor «desapareció». | Está suspendido, no cerrado. | fg. |
There are stopped jobs al salir. | Quedan trabajos suspendidos. | jobs, y cerrarlos con fg. |
fg: no such job, aunque el proceso existe. | Se lanzó desde otra shell: no es un trabajo de esta. | Actuar por PID, con kill. |
kill: %1: no such job | Lo mismo; o el trabajo ya terminó. | jobs para ver cuáles hay. |
kill 7702 «no hizo nada». | kill no escribe cuando la señal se envió bien. | Comprobar con ps -p 7702. |
| La tarea murió al cerrar la ventana. | Recibió SIGHUP. | nohup, disown o tmux. |
El & quedó en medio y parte de la línea no se ejecutó como se esperaba. | & termina el comando: lo que sigue es otro. | El & va al final, después de las redirecciones. |
pkill terminó programas de más. | El patrón coincidía con parte de otros nombres. | pgrep -a antes; pkill -x. |
Hay un archivo nohup.out que no creé. | Lo creó nohup, con la salida del comando. | Redirigir la salida a un archivo elegido. |
La referencia del tema:
| Se escribe | Qué hace |
|---|---|
comando & | Ejecuta en segundo plano. |
jobs, jobs -l | Lista los trabajos de esta shell; con sus PID. |
| Ctrl+Z | Suspende el trabajo en primer plano. |
bg, bg %n | Reanuda un trabajo en segundo plano. |
fg, fg %n | Trae un trabajo al primer plano. |
$! | El PID del último trabajo lanzado en segundo plano. |
| Ctrl+C, Ctrl+\ | Envían SIGINT y SIGQUIT al trabajo en primer plano. |
kill PID, kill %n | Envía SIGTERM. |
kill -9, -STOP, -CONT, -HUP | Envía otra señal. |
kill -l | Lista las señales. |
pkill nombre, killall nombre | Envía una señal por nombre de programa. |
nohup comando & | Lo protege del cierre de la terminal. |
disown %n | Lo quita de la lista de trabajos de la shell. |
nice -n N comando, renice -n N -p PID | Lanza con prioridad ajustada; la cambia. |
timeout T comando | Lo termina si excede el tiempo. |
wait | Espera a los trabajos en segundo plano. |
Antes de seguir conviene dejar todo limpio: pkill -u $USER sleep termina los sleep que hayan quedado, incluidos los de nohup, y rm nohup.out borra el archivo que este creó en el directorio de práctica. Al adoptar un proceso huérfano dijimos que había heredado todo de su padre; entre lo heredado hay algo que todavía no miramos de cerca. Cada proceso recibe al nacer un conjunto de variables que condicionan su comportamiento: dónde buscar los programas, en qué idioma hablar, qué editor abrir. Es el entorno, y es el tema 34.
15. Resumen
- Un comando en primer plano ocupa la terminal hasta terminar; uno en segundo plano se ejecuta mientras la shell sigue disponible.
comando &lo lanza en segundo plano. La shell responde con el número de trabajo y el PID.- Un trabajo es un comando o una tubería que la shell controla.
jobslos lista; se los nombra con%1,%2, y valen solo en la shell que los lanzó. - Ctrl+Z suspende el trabajo en primer plano;
bglo reanuda en segundo plano;fglo trae al primero.Stoppedsignifica en pausa, no terminado. - Un trabajo en segundo plano sigue escribiendo en la terminal: hay que redirigir su salida. Si intenta leer del teclado, queda suspendido.
- Una señal es un aviso numerado que se envía a un proceso. El proceso puede atenderla, ignorarla o dejar que actúe la acción por defecto.
- Ctrl+C envía
SIGINT, y Ctrl+Z,SIGTSTP, al trabajo en primer plano. killenvía señales por PID o por número de trabajo; sin indicar otra,SIGTERM. Solo a los procesos propios.SIGTERMpide terminar y permite al programa cerrar ordenadamente.SIGKILL,kill -9, lo elimina sin aviso: se usa solo si lo primero no funcionó.SIGSTOPySIGCONTcongelan y reanudan cualquier proceso.SIGKILLySIGSTOPno se pueden ignorar.pkillykillallenvían señales por nombre. Antes depkill, el mismo patrón conpgrep -a.- Al cerrar la terminal, sus trabajos reciben
SIGHUPy terminan.nohup comando &ydisownlo evitan. niceyrenicerebajan la prioridad de un proceso;timeoutlimita su duración;waitespera a los trabajos del fondo.
16. Preguntas de repaso
Intenta responder cada una antes de abrir la respuesta.
1. ¿Qué diferencia hay entre ejecutar un comando en primer plano y en segundo plano?
En primer plano, la shell espera a que termine y el comando recibe el teclado. En segundo plano, la shell devuelve el indicador de inmediato y el comando se ejecuta sin ocupar la terminal.
2. Al ejecutar sleep 100 & la shell responde [1] 8120. ¿Qué son los dos números?
El 1 es el número de trabajo, que le asigna esa shell. El 8120 es el PID, que identifica al proceso en todo el sistema.
3. ¿Qué diferencia hay entre un trabajo y un proceso?
El proceso lo administra el kernel y se identifica con un PID válido en todo el sistema. El trabajo lo administra la shell, vale solo en ella, y puede agrupar varios procesos, como los de una tubería.
4. Lanzaste un comando largo y olvidaste el &. ¿Cómo lo pasas al segundo plano sin perder lo hecho?
Con Ctrl+Z, que lo suspende, y luego bg, que lo reanuda en segundo plano.
5. Un trabajo figura como Stopped. ¿Terminó?
No. Está suspendido, en pausa, con su estado intacto. Se lo reanuda con fg o bg.
6. Pulsaste Ctrl+Z en vim y apareció el indicador. ¿Qué pasó, y cómo vuelves?
El editor quedó suspendido como un trabajo de la shell, con el archivo abierto. Se vuelve con fg.
7. ¿Por qué conviene redirigir la salida de un comando que se lanza en segundo plano?
Porque sigue conectada a la terminal, y lo que escriba aparecerá mezclado con lo que se esté haciendo.
8. ¿Qué es una señal, y qué puede hacer un proceso al recibirla?
Es un aviso numerado que se envía a un proceso. El proceso puede atenderla con una respuesta propia, ignorarla, o dejar que se aplique la acción por defecto, que suele ser terminar.
9. ¿Qué señales envían Ctrl+C y Ctrl+Z, y a quién?
Ctrl+C envía SIGINT y Ctrl+Z envía SIGTSTP, las dos al trabajo que está en primer plano en esa terminal.
10. ¿Qué señal envía kill 4321?
SIGTERM, la número 15: un pedido de terminar.
11. ¿Por qué no conviene usar kill -9 como primera opción?
Porque SIGKILL elimina el proceso sin que este pueda guardar datos, cerrar archivos ni limpiar temporales. Primero se envía SIGTERM, que le permite terminar ordenadamente.
12. ¿Qué dos señales no puede ignorar ni atender un proceso?
SIGKILL y SIGSTOP: las ejecuta el kernel directamente.
13. ¿Qué precaución hay que tomar antes de un pkill?
Ejecutar el mismo patrón con pgrep -a, para ver exactamente a qué procesos alcanzaría, ya que el patrón coincide con cualquier parte del nombre.
14. ¿Por qué un trabajo en segundo plano termina al cerrar la terminal, y cómo se evita?
Porque recibe la señal SIGHUP. Se evita lanzándolo con nohup comando &, o aplicándole disown si ya está en marcha.
15. Un comando terminó con código de salida 137. ¿Qué le ocurrió?
Fue terminado por la señal 9, SIGKILL: 137 es 128 más 9.
Para practicar
- Lanza tres
sleepde distinta duración en segundo plano. Míralos conjobs -ly conps. Abre otra terminal y ejecuta allíjobsypgrep -a sleep. Explica la diferencia. - Ejecuta
sleep 200en primer plano. Suspéndelo, mira su estado conjobsy conps -o pid,stat,cmd, reanúdalo en segundo plano, tráelo al frente e interrúmpelo. Comprueba el código de salida conecho $?. - Abre
tareas.txtconnanoovim, suspende el editor, ejecutalsyjobs, y vuelve al editor confg. Luego intenta salir de la shell con el editor suspendido y lee el mensaje. - Ejecuta
ls -R /usr &y observa qué ocurre con la terminal. Repítelo redirigiendo la salida a/tmp/usr.txt, y sigue su avance conwc -l. - Ejecuta
cat &. ¿En qué estado queda, y por qué? Tráelo al primer plano, escribe una línea y termínalo con Ctrl+D. - Lanza
sleep 500 &y envíale, en orden,STOP,CONTyTERM, mirando la columnaSTATdepsdespués de cada una. Repite terminándolo conINTy conKILL, y compara los avisos de la shell. - Lanza cuatro
sleepcon duraciones distintas. Conpgrep -aypkill -f, termina solo uno; luego, con un solo comando, los demás. - Lanza
nohup sleep 1000 &, cierra la ventana, abre otra y encuéntralo. Averigua su PPID y su terminal, y termínalo. Repite la experiencia sinnohup. - Lanza
nice -n 15 sleep 3000 &y verifica su valor conps -o pid,ni,cmd. Intenta llevarlo a 19 y luego a 0 conrenice. Localízalo entopohtopy míralo en la columnaNI. - Prueba
timeout 2 sleep 10; echo $?ytimeout -s KILL 2 sleep 10; echo $?. Explica los dos códigos. Luego mide contimecuánto tardasleep 2 & sleep 2 & wait.