¿Por qué bash muestra 'Terminado' después de matar un proceso?

17

Aquí está el comportamiento que quiero entender:

$ ps
  PID TTY           TIME CMD
  392 ttys000    0:00.20 -bash
 4268 ttys000    0:00.00 xargs
$ kill 4268
$ ps
  PID TTY           TIME CMD
  392 ttys000    0:00.20 -bash
[1]+  Terminated: 15          xargs
$ ps
  PID TTY           TIME CMD
  392 ttys000    0:00.21 -bash

¿Por qué muestra el mensaje [1]+ Terminated: 15 xargsdespués de que elimine un proceso, en lugar de no mostrarlo como acaba de finalizar?

Estoy usando bash en Mac OS X 10.7.5.

sintagma
fuente

Respuestas:

24

Respuesta corta

En bash(y dash) los diversos mensajes de "estado del trabajo" no se muestran desde los manejadores de señales, pero requieren una verificación explícita. Esta verificación se realiza solo antes de que se proporcione una nueva solicitud, probablemente para no molestar al usuario mientras está escribiendo un nuevo comando.

El mensaje no se muestra justo antes de la solicitud después de que killse muestre probablemente porque el proceso aún no está muerto; esta es una condición particularmente probable ya que killes un comando interno del shell, por lo que es muy rápido de ejecutar y no necesita bifurcación.

En su lugar, hacer el mismo experimento killallgeneralmente produce el mensaje "asesinado" inmediatamente, firme que el tiempo / cambios de contexto / lo que sea necesario para ejecutar un comando externo causan un retraso lo suficientemente largo como para que el proceso se elimine antes de que el control regrese al shell .

matteo@teokubuntu:~$ dash
$ sleep 60 &
$ ps
  PID TTY          TIME CMD
 4540 pts/3    00:00:00 bash
 4811 pts/3    00:00:00 sh
 4812 pts/3    00:00:00 sleep
 4813 pts/3    00:00:00 ps
$ kill -9 4812
$ 
[1] + Killed                     sleep 60
$ sleep 60 &
$ killall sleep
[1] + Terminated                 sleep 60
$ 

Respuesta larga

dash

En primer lugar, eché un vistazo a las dashfuentes, ya que dashexhibe el mismo comportamiento y el código es seguramente más simple que bash.

Como se dijo anteriormente, el punto parece ser que los mensajes de estado del trabajo no se emiten desde un controlador de señal (que puede interrumpir el flujo de control de shell "normal"), sino que son la consecuencia de una verificación explícita (una showjobs(out2, SHOW_CHANGED)llamada dash) que se realiza solo antes de solicitar una nueva entrada del usuario, en el bucle REPL.

Por lo tanto, si el shell está bloqueado esperando la entrada del usuario, no se emite dicho mensaje.

Ahora, ¿por qué la verificación realizada justo después de matar no muestra que el proceso realmente haya terminado? Como se explicó anteriormente, probablemente porque es demasiado rápido. killes un comando interno del shell, por lo que es muy rápido de ejecutar y no necesita bifurcación, por lo tanto, inmediatamente después de que killse realiza la verificación, el proceso todavía está vivo (o, al menos, todavía se está matando).


bash

Como era de esperar, al bashser un caparazón mucho más complejo, era más complicado y requería un poco de gdbfu.

La traza inversa para cuando se emite ese mensaje es algo como

(gdb) bt
#0  pretty_print_job (job_index=job_index@entry=0, format=format@entry=0, stream=0x7ffff7bd01a0 <_IO_2_1_stderr_>) at jobs.c:1630
#1  0x000000000044030a in notify_of_job_status () at jobs.c:3561
#2  notify_of_job_status () at jobs.c:3461
#3  0x0000000000441e97 in notify_and_cleanup () at jobs.c:2664
#4  0x00000000004205e1 in shell_getc (remove_quoted_newline=1) at /Users/chet/src/bash/src/parse.y:2213
#5  shell_getc (remove_quoted_newline=1) at /Users/chet/src/bash/src/parse.y:2159
#6  0x0000000000423316 in read_token (command=<optimized out>) at /Users/chet/src/bash/src/parse.y:2908
#7  read_token (command=0) at /Users/chet/src/bash/src/parse.y:2859
#8  0x00000000004268e4 in yylex () at /Users/chet/src/bash/src/parse.y:2517
#9  yyparse () at y.tab.c:2014
#10 0x000000000041df6a in parse_command () at eval.c:228
#11 0x000000000041e036 in read_command () at eval.c:272
#12 0x000000000041e27f in reader_loop () at eval.c:137
#13 0x000000000041c6fd in main (argc=1, argv=0x7fffffffdf48, env=0x7fffffffdf58) at shell.c:749

La llamada que verifica los trabajos muertos y co. es notify_of_job_status(es más o menos el equivalente de showjobs(..., SHOW_CHANGED)in dash); # 0- # 1 están relacionados con su funcionamiento interno; 6-8 es el código del analizador generado por yacc; 10-12 es el bucle REPL.

El lugar interesante aquí es el # 4, es decir, de donde notify_and_cleanupproviene la llamada. Parece que bash, a diferencia de esto dash, puede buscar trabajos terminados en cada carácter leído desde la línea de comando, pero esto es lo que encontré:

      /* If the shell is interatctive, but not currently printing a prompt
         (interactive_shell && interactive == 0), we don't want to print
         notifies or cleanup the jobs -- we want to defer it until we do
         print the next prompt. */
      if (interactive_shell == 0 || SHOULD_PROMPT())
    {
#if defined (JOB_CONTROL)
      /* This can cause a problem when reading a command as the result
     of a trap, when the trap is called from flush_child.  This call
     had better not cause jobs to disappear from the job table in
     that case, or we will have big trouble. */
      notify_and_cleanup ();
#else /* !JOB_CONTROL */
      cleanup_dead_jobs ();
#endif /* !JOB_CONTROL */
    }

Por lo tanto, en modo interactivo, es intencional retrasar la verificación hasta que se proporcione una nueva solicitud, probablemente para no molestar al usuario que ingresa los comandos. En cuanto a por qué la verificación no detecta el proceso inactivo cuando se muestra el nuevo aviso inmediatamente después del kill, la explicación anterior es válida (el proceso aún no está inactivo).

Matteo Italia
fuente
5

Para evitar cualquier mensaje de terminación de trabajo (en la línea de comando, así como en la pssalida), puede colocar el comando en segundo plano en una sh -c 'cmd &'construcción.

{
ps
echo
pid="$(sh -c 'sleep 60 1>&-  & echo ${!}')"
#pid="$(sh -c 'sleep 60 1>/dev/null  & echo ${!}')"
#pid="$(sh -c 'sleep 60 & echo ${!}' | head -1)"
ps
kill $pid
echo
ps
}

Por cierto, es posible recibir notificaciones inmediatas de terminación de trabajo bashutilizando las opciones de shell set -bo set -o notifyrespectivamente.

En este caso, " bashrecibe una SIGCHLDseñal y su manejador de señal muestra el mensaje de notificación de inmediato, incluso si bashactualmente está en el medio de la espera de que se complete un proceso en primer plano" (consulte la siguiente referencia a continuación).

Para obtener un tercer modo de notificación de control de trabajo entre set +b(el modo predeterminado) y set -b(para que reciba notificaciones inmediatas de finalización del trabajo sin corromper lo que ya ha escrito en su línea de comando actual, similar a ctrl-x ctrl-v) requiere un parche bashpor Simon Tatham (para el parche en sí y más información, consulte: Notificación de trabajo asíncrono sensible en bash (1) ).

Entonces, repitamos Matteo Italia's gdb-fu para un bashshell que se ha configurado para notificar inmediatamente la finalización del trabajo set -b.

# 2 Terminal.app windows

# terminal window 1
# start Bash compiled with -g flag
~/Downloads/bash-4.2/bash -il
set -bm
echo $$ > bash.pid

# terminal window 2
gdb -n -q
(gdb) set print pretty on
(gdb) set history save on
(gdb) set history filename ~/.gdb_history
(gdb) set step-mode off
(gdb) set verbose on
(gdb) set height 0
(gdb) set width 0
(gdb) set pagination off
(gdb) set follow-fork-mode child
(gdb) thread apply all bt full
(gdb) shell cat bash.pid
(gdb) attach <bash.pid>
(gdb) break pretty_print_job

# terminal window 1
# cut & paste
# (input will be invisible on the command line)
sleep 600 &   

# terminal window 2
(gdb) continue
(gdb) ctrl-c

# terminal window 1
# cut & paste
kill $!

# terminal window 2
(gdb) continue
(gdb) bt

Reading in symbols for input.c...done.
Reading in symbols for readline.c...done.
Reading in symbols for y.tab.c...done.
Reading in symbols for eval.c...done.
Reading in symbols for shell.c...done.
#0  pretty_print_job (job_index=0, format=0, stream=0x7fff70bb9250) at jobs.c:1630
#1  0x0000000100032ae3 in notify_of_job_status () at jobs.c:3561
#2  0x0000000100031e21 in waitchld (wpid=-1, block=0) at jobs.c:3202
#3  0x0000000100031a1a in sigchld_handler (sig=20) at jobs.c:3049
#4  <signal handler called>
#5  0x00007fff85a9f464 in read ()
#6  0x00000001000b39a9 in rl_getc (stream=0x7fff70bb9120) at input.c:471
#7  0x00000001000b3940 in rl_read_key () at input.c:448
#8  0x0000000100097c88 in readline_internal_char () at readline.c:517
#9  0x0000000100097dba in readline_internal_charloop () at readline.c:579
#10 0x0000000100097de6 in readline_internal () at readline.c:593
#11 0x0000000100097842 in readline (prompt=0x100205f80 "noname:~ <yourname>$ ") at readline.c:342
#12 0x0000000100007ab7 in yy_readline_get () at parse.y:1443
#13 0x0000000100007bbe in yy_readline_get () at parse.y:1474
#14 0x00000001000079d1 in yy_getc () at parse.y:1376
#15 0x000000010000888d in shell_getc (remove_quoted_newline=1) at parse.y:2231
#16 0x0000000100009a22 in read_token (command=0) at parse.y:2908
#17 0x00000001000090c1 in yylex () at parse.y:2517
#18 0x000000010000466a in yyparse () at y.tab.c:2014
#19 0x00000001000042fb in parse_command () at eval.c:228
#20 0x00000001000043ef in read_command () at eval.c:272
#21 0x0000000100004088 in reader_loop () at eval.c:137
#22 0x0000000100001e4d in main (argc=2, argv=0x7fff5fbff528, env=0x7fff5fbff540) at shell.c:749

(gdb) detach
(gdb) quit
phron
fuente
¡frio! ¿Pero crees que podría haber alguna otra manera? Estoy intentando esto: pid="$(sh -c 'cat "$fileName" |less & echo ${!}')"pero no aparecerán menos
Aquarius Power