Почему bash показывает «Прервано» после завершения процесса?

17

Вот поведение, которое я хочу понять:

$ 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

Почему он показывает [1]+ Terminated: 15 xargsпосле того, как я убил процесс, вместо того, чтобы просто не показывать, как он был убит?

Я использую Bash на Mac OS X 10.7.5.

синтагма
источник

Ответы:

24

Короткий ответ

В bashdash) различные сообщения «статус работы» не отображаются из обработчиков сигналов, но требуют явной проверки. Эта проверка выполняется только перед предоставлением нового приглашения, вероятно, чтобы не беспокоить пользователя, пока он вводит новую команду.

Сообщение не отображается непосредственно перед приглашением после того, killкак отображается, вероятно, потому, что процесс еще не завершен - это особенно вероятное условие, поскольку killэто внутренняя команда оболочки, поэтому она очень быстро выполняется и не требует разветвления.

Выполнение того же эксперимента killallвместо этого обычно приводит к немедленному выводу сообщения «kill», что означает, что переключение времени / контекста / все, что требуется для выполнения внешней команды, вызывает задержку, достаточную для того, чтобы процесс был убит, прежде чем элемент управления вернется в оболочку ,

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
$ 

Длинный ответ

dash

Прежде всего, я взглянул на dashисточники, так какdash демонстрирует то же поведение, и код, безусловно, проще, чем bash.

Как уже было сказано выше, похоже, дело в том, что сообщения о состоянии задания не отправляются из обработчика сигнала (который может прерывать «нормальный» поток управления оболочкой), но они являются следствием явной проверки ( showjobs(out2, SHOW_CHANGED) вызова dash), которая выполняется только перед запросом нового ввода от пользователя, в цикле REPL.

Таким образом, если оболочка заблокирована в ожидании ввода пользователя, такое сообщение не отправляется.

Теперь, почему проверка, выполненная сразу после уничтожения, не показывает, что процесс был фактически завершен? Как объяснено выше, вероятно, потому что это слишком быстро. killэто внутренняя команда оболочки, поэтому она выполняется очень быстро и не требует разветвления, поэтому, когда сразу после killвыполнения проверки, процесс все еще жив (или, по крайней мере, все еще уничтожается).


bash

Как и ожидалось, bash будучи гораздо более сложной оболочкой, было сложнее и требовало некоторого gdb-fu.

Обратный след для того, когда это сообщение испускается, является чем-то вроде

(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

Звонок, который проверяет на мертвые рабочие места & co. есть notify_of_job_status(это более или менее эквивалентно showjobs(..., SHOW_CHANGED)вdash ); # 0- # 1 связаны с его внутренней работой; 6-8 - код парсера, сгенерированный yacc; 10-12 - цикл REPL.

Интересное место здесь # 4, то есть откуда notify_and_cleanupприходит звонок. Кажется, что bash, в отличие от dash, может проверять завершенные задания при каждом чтении символов из командной строки, но вот что я нашел:

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

Таким образом, в интерактивном режиме намеренно откладывать проверку до получения нового приглашения, вероятно, чтобы не мешать вводу команд пользователем. Что касается того, почему проверка не обнаруживает мертвый процесс при отображении нового приглашения сразу после kill, сохраняется предыдущее объяснение (процесс еще не мертв).

Matteo Italia
источник
5

Чтобы избежать каких-либо сообщений о завершении задания (как в командной строке, так и в psвыходных данных), вы можете поместить команду, которая будет перенесена в sh -c 'cmd &'конструкцию.

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

Кстати, можно получить немедленные уведомления о прекращении работы bashс помощью параметров оболочкиset -b или set -o notifyсоответственно.

В этом случае « bashполучает SIGCHLDсигнал, а его обработчик сигнала немедленно отображает сообщение уведомления - даже еслиbash в данный момент находится в середине ожидания завершения процесса переднего плана» (см. Следующую ссылку ниже).

Чтобы получить третий режим промежуточного уведомления о контроле за работой set +b(режим по умолчанию) и set -b(чтобы вы получали немедленные уведомления о прекращении работы, не повреждая то, что вы уже набрали в текущей командной строке - аналогично ctrl-x ctrl-v), требуется патч bashот Simon Tatham (для Сам патч и дополнительную информацию смотрите в разделе: Разумное уведомление об асинхронном задании в bash (1) ).

Так что давайте просто повторим Matteo Italia's gdb-fu для bashоболочки, которая была настроена на немедленное уведомление о прекращении работы 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
источник
круто! но вы верите, что может быть какой-то другой путь? Я пытаюсь это: pid="$(sh -c 'cat "$fileName" |less & echo ${!}')"но меньше не появятся
Водолей Power