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

В настоящее время я использую этот код:

#! /bin/bash

# local preparations
# ...

ssh -t $USER@remote.far <<-'COMMANDS'

    echo "Preparing execution"

    java -jar execute.jar &
    executePID=$!

    echo "Ready."
    echo "CTRL+C to clean and close"

    trap "kill $executePID" INT HUP
    wait $executePID

    #cleanup code here

    echo "done. Logging out"
    sleep 2
    logout
COMMANDS

# Final local cleanup

Этот код, кажется, работает, просто найдите, пока команда wait (встроенная). Кажется, что wait потребляет все команды, которые идут после него, и поэтому, когда я пытаюсь отправить SIGINT (Ctrl+C), кажется, что он не может выполнить содержимое прерывания и весь код очистки.

Как я могу это исправить, чтобы все работало так, как я ожидаю?

Мне не разрешено разбивать этот bash-файл на несколько, и мне не разрешается создавать какие-либо сценарии на удаленном компьютере, даже если они временные.
Оба компьютера работают под управлением Linux.

1 ответ1

1

объяснение

Дело не в wait . Я думаю, что это то, что происходит:

Вы используете << , поэтому stdin из ssh перенаправляется на некоторый дескриптор, через который проходит весь документ .

Этот другой ответ объясняет, как ssh может захватывать Ctrl+C при использовании -t . Вот что важно:

На стороне клиента ssh попытается установить tty используемый stdin в режим "raw" […]. Установка режима raw означает, что символы, которые обычно посылают сигналы (такие как Ctrl+C), вместо этого просто вставляются во входной поток.

В вашем случае это не то же stdin локального использования оболочки. tty используемый вашей локальной оболочкой, остается без изменений, он никогда не устанавливается в "сырой" режим.

Поэтому, когда вы нажимаете Ctrl+C, он действует локально и завершает работу ssh . В этот момент удаленная сторона получает SIGHUP . Ваша trap работает и убивает java . Я думаю, здесь есть подводный камень: trap выполняет некоторый код в ответ на данный сигнал, но не мешает нормальному эффекту сигнала. Поэтому мне кажется, что ваша java будет убита даже без trap потому что это работа оболочки, которая завершается в ответ на SIGHUP .

Завершенная оболочка прекращает чтение и интерпретацию своего стандартного stdin . Вот почему все, что следует за wait , отбрасывается.


Решение

commands() { cat <<-'COMMANDS'

    cleanup() {
    # cleanup code here
    echo "Done. Logging out"
    sleep 2
    logout
    }

    echo "Preparing execution"

    java -jar execute.jar &
    executePID=$!

    echo "Ready."
    echo "CTRL+C to clean and close"

    trap "kill $executePID; cleanup" INT HUP
    wait $executePID

    cleanup
COMMANDS

}

stty raw -echo; cat <(commands) - | ssh -t $USER@remote.far; stty -raw echo

Последняя строка - фактическая команда. Сначала мы подготавливаем tty чтобы Ctrl+C не мог действовать локально. Затем мы объединяем команды и стандартный ввод, передаем его в удаленную оболочку. Я не могу сделать это с этим документом напрямую, command функция - это обходной путь (было бы проще использовать обычный файл, но вы сказали, что не можете использовать более одного). После выхода из ssh и cat мы устанавливаем tty в его нормальное состояние.

Наличие псевдотерминала очень важно, поэтому убедитесь, что ssh -t работает (при необходимости используйте -tt ).

Удаленная оболочка должна читать (буферизовать) все команды из commands , только тогда она может получить Ctrl+C, когда вы ее нажмете. Я думаю, это означает, что вы не можете иметь слишком много кода после wait . Кроме того, все, что вы хотите сделать после SIGINT должно быть запущено из trap . По этим причинам я использовал одну функцию cleanup которая делает все это.

Код не является надежным. Нажмите Ctrl+C слишком рано или несколько раз, и вы окажетесь в удаленной оболочке или с несколько "сломанным" локальным tty . В этом последнем случае используйте команду reset для сброса вашего tty .

Вы должны нажать клавишу (например, Enter) после того, как увидите «Соединение с… закрыто». Причина в том, что cat не заметит, что канал не работает (из-за того, что ssh больше не существует), пока не попытается что-то записать в него.


альтернатива

Если по какой-либо причине вышеуказанное решение не работает для вас, воспользуйтесь этой более простой альтернативой. Здесь документ точно такой же, как и раньше. В этом случае мы не связываемся с tty , Ctrl+C завершает локальный ssh как это происходит с вашим исходным кодом. Разница (по отношению к вашему коду) в том, что trap выполняет уборку. Я думаю, что этого будет достаточно, чтобы поймать только SIGHUP .

ssh -t $USER@remote.far <<-'COMMANDS'

    cleanup() {
    # cleanup code here
    echo "Done. Logging out"
    sleep 2
    logout
    }

    echo "Preparing execution"

    java -jar execute.jar &
    executePID=$!

    echo "Ready."
    echo "CTRL+C to clean and close"

    trap "kill $executePID; cleanup" INT HUP
    wait $executePID

    cleanup
COMMANDS

Примечание: когда срабатывает trap cleanup выводит сообщение, но вы его не увидите, потому что ваш локальный ssh уже отключен. Вы увидите сообщение, только если java выйдет без trap . Тем не менее ваш код очистки (# cleanup code here) должен быть выполнен.

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .