Ответ 1
О, это хороший.
Это связано с тем, как работают группы процессов, как bash ведет себя при вызове в качестве неинтерактивной оболочки с -c
и эффектом &
в командах ввода.
Ответ предполагает, что вы знакомы с тем, как работает управление заданиями в UNIX; если вы этого не сделаете, вот представление высокого уровня: каждый процесс принадлежит к группе процессов (процессы в той же группе часто помещаются там как часть конвейера команд, например cat file | sort | grep 'word'
помещает процессы, выполняемые cat(1)
, sort(1)
и grep(1)
в той же группе процессов). bash
- это процесс, подобный любому другому, и он также относится к группе процессов. Группы процессов являются частью сеанса (сеанс состоит из одной или нескольких групп процессов). В сеансе имеется не более одной группы процессов, называемой группой процессов переднего плана и, возможно, многими группами фонового процесса. Группа процессов переднего плана управляет терминалом (если имеется терминал управления, подключенный к сеансу); руководитель сеанса (bash) перемещает процессы от фона на передний план и от переднего плана до фона с помощью tcsetpgrp(3)
. Сигнал, отправленный в группу процессов, доставляется каждому процессу в этой группе.
Если концепция групп процессов и управления заданиями для вас совершенно новая, я думаю, вам нужно будет прочитать об этом, чтобы полностью понять этот ответ. Большой ресурс для изучения этого - глава 9 расширенного программирования в среде UNIX (3-е издание).
Сказав это, посмотрим, что здесь происходит. Мы должны собрать каждый кусочек головоломки.
В обоих случаях удаленная сторона ssh вызывает bash(1)
с помощью -c
. Флаг -c
вызывает bash(1)
для запуска в качестве неинтерактивной оболочки. С manpage:
Интерактивная оболочка запускается без аргументов без опций и без опции -c, стандартный ввод и ошибка которой оба подключен к терминалам (как определено isatty (3)), или один запущен с опцией -i. PS1 установлен, а $- включает i, если bashинтерактивный, разрешая оболочку script или загрузочный файл для проверки этого состояние.
Кроме того, важно знать, что контроль заданий отключен, когда bash запущен в неинтерактивном режиме. Это означает, что bash не будет создавать отдельную группу процессов для запуска команды, так как управление заданиями отключено, нет необходимости переместить эту команду между передним и задним фонами, чтобы она могла оставаться в одной и той же группе процессов как bash. Это произойдет, если вы принудительно назначили PTY на ssh с помощью -t
.
Однако использование &
имеет побочный эффект, заставляя оболочку не ждать завершения команды (даже если управление заданиями отключено). С manpage:
Если команда завершается управляющим оператором &, оболочка выполняет команду в фоновом режиме в подоболочке. Оболочка не дожидаться завершения команды, а статус возврата - 0. Команды, разделенные символом a; выполняются последовательно; оболочка ждет для каждой команды для завершения по очереди. Возвращаемым статусом является выход статус последней выполненной команды.
Итак, в обоих случаях bash не будет ждать выполнения команды, а touch(1)
будет выполняться в той же группе процессов, что и bash(1)
.
Теперь рассмотрим, что произойдет, когда лидер сеанса завершит работу. Цитирование из setpgid(2)
manpage:
Если сеанс имеет управляющий терминал и флаг CLOCAL для этого терминал не установлен, и происходит зависание терминала, затем сеанс лидер отправляется SIGHUP. Если лидер сеанса выходит, то SIGHUP сигнал будет также отправляться каждому процессу в процессе переднего плана группа управляющего терминала.
(Акцент мой)
Если вы не используете -t
Если вы не используете -t
, на удаленной стороне не назначается PTY, поэтому bash не является лидером сеанса, и на самом деле новый сеанс не создается. Поскольку sshd работает как демон, процесс bash, который имеет forked + exec() 'd, не будет иметь управляющий терминал. Таким образом, несмотря на то, что оболочка завершается очень быстро (вероятно, до touch(1)
), в группу процессов не отправляется SIGHUP
, потому что bash не был лидером сеанса (и нет управляющего терминала). Итак, все работает.
Когда вы используете -t
-t
принудительно выделяет PTY, что означает, что удаленная сторона ssh вызовет setsid(2)
, выделит псевдотерминал + для нового процесса с помощью forkpty(3)
, подключит вход и выход главного устройства PTY к конечным точкам сокета которые приводят к вашей машине, и, наконец, выполните bash(1)
. forkpty(3)
открывает ведомую сторону PTY в разветвленном процессе, который станет bash; поскольку там нет управляющего терминала для текущего сеанса, а терминальное устройство открыто, устройство PTY становится управляющим терминалом для сеанса, а bash становится лидером сеанса.
Затем происходит одно и то же: touch(1)
выполняется в той же группе процессов и т.д., yadda yadda. Дело в том, что на этот раз есть руководитель сеанса и контрольный терминал. Итак, поскольку bash не беспокоится о ожидании из-за &
, когда он выходит, SIGHUP
доставляется в группу процессов, а touch(1)
умирает преждевременно.
О nohup
nohup(1)
здесь не работает, потому что все еще есть состояние гонки. Если bash(1)
завершается до того, как nohup(1)
имеет возможность настроить необходимую обработку сигнала и перенаправление файлов, он не будет иметь никакого эффекта (что, вероятно, происходит)
Возможное исправление
Сильное повторное включение управления заданиями исправляет это. В bash вы делаете это с помощью set -m
. Это работает:
ssh -t localhost 'set -m ; touch foobar &'
Или принудительно bash дождаться завершения touch(1)
:
ssh -t localhost 'touch foobar & wait `pgrep touch`'