Por que o bash mostra 'Terminated' depois de matar um processo?

17

Aqui está o comportamento que eu quero 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 que mostra [1]+ Terminated: 15 xargsdepois que eu mato um processo, em vez de simplesmente não mostrar como foi acabado?

Estou usando o bash no Mac OS X 10.7.5.

sintagma
fonte

Respostas:

24

Resposta curta

Em bash(e dash) as várias mensagens de "status do trabalho" não são exibidas nos manipuladores de sinal, mas requerem uma verificação explícita. Essa verificação é realizada apenas antes que um novo prompt seja fornecido, provavelmente para não incomodar o usuário enquanto ele estiver digitando um novo comando.

A mensagem não é mostrada antes do prompt após a killexibição provavelmente porque o processo ainda não está morto - essa é uma condição particularmente provável, pois killé um comando interno do shell, portanto, é muito rápido de executar e não precisa de bifurcação.

Em killallvez disso, fazer o mesmo experimento normalmente gera a mensagem "morto" imediatamente, assine que o tempo / contexto alterna / o que for necessário para executar um comando externo causa um atraso longo o suficiente para que o processo seja morto antes que o controle retorne ao 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
$ 

Resposta longa

dash

Antes de tudo, observei as dashfontes, pois dashexibe o mesmo comportamento e o código é certamente mais simples que bash.

Como dito acima, o ponto parece ser que as mensagens de status da tarefa não são emitidas de um manipulador de sinal (que pode interromper o fluxo de controle de shell "normal"), mas são a consequência de uma verificação explícita (uma showjobs(out2, SHOW_CHANGED)chamada dash) realizada somente antes de solicitar nova entrada do usuário, no loop REPL.

Portanto, se o shell estiver bloqueado aguardando a entrada do usuário, nenhuma mensagem será emitida.

Agora, por que a verificação realizada logo após a matança mostra que o processo foi realmente encerrado? Como explicado acima, provavelmente porque é muito rápido. killé um comando interno do shell, por isso é muito rápido de executar e não precisa de bifurcação, portanto, quando imediatamente após killa verificação é realizada, o processo ainda está ativo (ou, pelo menos, ainda está sendo morto).


bash

Como esperado, bashsendo um shell muito mais complexo, era mais complicado e exigia um pouco de gdb-fu.

O retorno para quando essa mensagem é emitida é 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

A chamada que verifica se há trabalhos mortos e companhia. é notify_of_job_status(é mais ou menos o equivalente a showjobs(..., SHOW_CHANGED)in dash); # 0- # 1 estão relacionados ao seu trabalho interno; 6-8 é o código do analisador gerado pelo yacc; 10-12 é o loop REPL.

O lugar interessante aqui é o número 4, ou seja, de onde a notify_and_cleanupchamada chega. Parece que bash, ao contrário dash, pode verificar trabalhos terminados em cada caractere lido na linha de comando, mas eis o que eu encontrei:

      /* 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 */
    }

Portanto, no modo interativo, é intencional adiar a verificação até que um novo prompt seja fornecido, provavelmente não perturbe o usuário digitando comandos. Quanto ao motivo pelo qual a verificação não identifica o processo morto ao exibir o novo prompt imediatamente após kill, a explicação anterior é válida (o processo ainda não está morto).

Matteo Italia
fonte
5

Para evitar qualquer mensagem de encerramento de tarefa (na linha de comando e na pssaída), você pode colocar o comando em segundo plano em uma sh -c 'cmd &'construção.

{
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
}

A propósito, é possível obter notificações imediatas de término de tarefas bashusando as opções de shell set -bou set -o notifyrespectivamente.

Nesse caso, " bashrecebe um SIGCHLDsinal e seu manipulador de sinais exibe a mensagem de notificação imediatamente - mesmo que bashesteja no momento aguardando a conclusão de um processo em primeiro plano" (consulte a próxima referência abaixo).

Para obter um terceiro modo de notificação de controle de tarefas entre set +b(o modo padrão) e set -b(para que você receba notificações imediatas de término de tarefas sem danificar o que você já digitou na linha de comando atual - semelhante a ctrl-x ctrl-v), é necessário um patch para bashSimon Tatham (para o patch em si e mais informações, consulte: Notificação assíncrona sensível do trabalho no bash (1) ).

Então, vamos repetir Matteo Italia's- gdbfu para um bashshell que foi configurado para notificar imediatamente o término do trabalho com 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
fonte
legal! mas você acredita que poderia ter outra maneira? Eu estou tentando isso: pid="$(sh -c 'cat "$fileName" |less & echo ${!}')"mas menos não aparecerá
Aquarius Power