bash-скрипт, выполненный по ssh, возвращает неправильный код завершения 0

Вопрос или проблема

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

Установка кода выхода явно работает, как и ожидалось:

~$ ssh host exit 5 && echo OK || echo FAIL
FAIL

Однако, если есть оболочка, сигнализирующая о некорректном выходе, ssh всегда возвращает 0 (скрипт имитируется выполнением строки):

~$ ssh host sh -c 'exit 5' && echo OK || echo FAIL
OK

Выполнение того же скрипта на хосте в интерактивной оболочке работает прекрасно:

~$ sh -c 'exit 5' && echo OK || echo FAIL
FAIL

Я запутался, почему это происходит. Как я могу заставить ssh передать код возврата bash? Я не могу изменить удаленные скрипты.

Я использую аутентификацию по открытым ключам, закрытый ключ разблокирован – нет необходимости в взаимодействии с пользователем. Все системы работают на Ubuntu 18.04. Версии приложений:

  • OpenSSH_7.6p1 Ubuntu-4ubuntu0.1, OpenSSL 1.0.2n 7 Dec 2017
  • GNU bash, Version 4.4.19(1)-release (x86_64-pc-linux-gnu)

Примечание: Этот вопрос отличается от этих, казалось бы, похожих вопросов:

Как отмечено в ответе, который вы уже имеете, удаленный sh не выполняет exit 5. Только exit:

$ ssh test sh -x -c 'exit 5'; echo $?
+ exit
0

Что происходит здесь, объясняется, например, в этом ответе:

ssh выполняет удаленную оболочку и передает строку в нее, а не список аргументов.

Когда мы выполняем ssh host sh -c 'exit 5':

  1. Локальная оболочка удаляет одинарные кавычки (удаление кавычек);
  2. Клиент ssh получает аргументы host, sh, -c и exit 5. Он объединяет их в строку и отправляет удаленному хосту;
  3. На удаленном хосте ssh вызывает оболочку и передает ей строку sh -c exit 5;
  4. Удаленная оболочка вызывает sh и передает ей опцию -c, exit как строку команды, и 5 как имя команды.

Обратите внимание, что если мы добавим слова после exit 5, они просто передаются в sh как дальнейшие аргументы – никаких ошибок, связанных с тем, что они не распознаются оболочкой:

$ ssh test sh -x -c 'exit 5' a b c; echo $?
+ exit
0

strace подтверждает, что 5 не является частью строки команды, переданной sh, здесь; это аргумент:

$ ssh test strace -e execve sh -c 'exit 5'; echo $?
execve("/usr/bin/sh", ["sh", "-c", "exit", "5"], 0x7ffc0d744c38 /* 14 vars */) = 0
+++ вышел с 0 +++
0

Чтобы выполнить sh -c 'command' на удаленном хосте, как задумано, мы должны убедиться, что правильно отправляем кавычки тоже:

$ ssh test "sh -x -c 'exit 5'"; echo $?
+ exit 5
5

Чтобы было ясно, что кавычение всей удаленной команды не имеет отношения к нашей текущей проблеме, мы могли бы просто написать:

$ ssh test sh -x -c "'exit 5'"; echo $?
+ exit 5
5

Экранирование внутренних кавычек с помощью обратных слэшей вместо двойных кавычек тоже сработает.


Примечание о команде ssh host sh -c ':; exit 5' (из комментариев к вашему вопросу). Что она делает:

$ ssh test sh -x -c ':; exit 5'; echo $?
+ :
5

То есть exit 5 выполняется внешней оболочкой, а не sh. Снова, чтобы позволить sh выйти с нужным кодом:

$ ssh test sh -x -c "':; exit 5'"; echo $?
+ :
+ exit 5
5

Я смог воспроизвести это, используя команду, которую вы использовали, и смог решить это, обернув удаленную команду в кавычки. Вот мои тестовые случаи:

#!/bin/bash -x

echo 'Тест без кавычек:'
ssh evil sh -x -c exit 5 && echo OK || echo FAIL

echo 'Тест с кавычками 1:'
ssh evil sh -x -c 'exit 5' && echo OK || echo FAIL

echo 'Тест с кавычками 2:'
ssh evil 'sh -x -c "exit 5"' && echo OK || echo FAIL

Вот результаты:

bash-[540]$ bash -x test.sh
+ echo 'Тест без кавычек:'
Тест без кавычек:
+ ssh evil sh -x -c exit 5
+ exit
+ echo OK
OK
+ echo 'Тест с кавычками 1:'
Тест с кавычками 1:
+ ssh evil sh -x -c 'exit 5'
+ exit
+ echo OK
OK
+ echo 'Тест с кавычками 2:'
Тест с кавычками 2:
+ ssh evil 'sh -x -c "exit 5"'
+ exit 5
+ echo FAIL
FAIL

В первом и втором тестах, кажется, 5 не передается в exit, как мы ожидали. Он просто, похоже, исчезает. Он не идет в exit, sh не жалуется на 5: команда не найдена, и ssh не жалуется на это.

В третьем тесте exit 5 заключено в кавычки внутри более крупной команды, чтобы запустить на удаленном хосте, так же как и во втором тесте. Это гарантирует, что 5 передается в exit, и оба выполняются как опция -c для sh. Разница между вторым и третьим тестами в том, что весь набор команд и аргументов отправляется на удаленный хост в кавычках как один аргумент командной строки для ssh.

Другие ответы хорошо отвечают на вопрос, опираясь на приведенные примеры. Мое реальное приложение более сложное и включает в себя серию скриптов и подпроцессов. Вот упрощенный пример скрипта, который я хочу выполнить:

#!/bin/bash
sub-process-that-fails
# сохранить и вывести код возврата для целей отладки
rc=$?
echo $rc
exit $rc

Пытаясь убедиться, что удаленно выполняемая оболочка действительно является bash, а не dash (как отметил @JeffSchaller), я попробовал вызвать скрипт вот так:

~$ ssh -t -t host /bin/bash -x /srv/scripts/run.sh ; echo $?

Что привело к этому странному выводу:

+ sub-process-that-fails
+ rc=5
+ echo 5
5
+ exit 5
0

После часов поиска я заметил, что в .bashrc был установлен trap 'kill 0' EXIT. Это сделано, чтобы убить все подпроцессы в случае, если bash будет убит. Отладка bash не отображает выполнение этой ловушки. Я переместил ловушку в обёрточный скрипт. Теперь я вижу, что на самом деле выполняется:

+ trap 'kill 0' EXIT
+ sub-process-that-fails
+ rc=5
5
+ echo 5
+ exit 5
+ kill 0
0

Удаленная оболочка выходит с кодом последней выполненной команды. Это kill 0, и она выходит с 0.

Ответ или решение

Решение проблемы: Неверный код выхода при выполнении bash скрипта через SSH

При автоматизации процессов, связанных с удалённым выполнением скриптов через SSH, важно правильно обработать коды выхода для эффективного обнаружения ошибок. В данном ответе мы рассмотрим, почему команда, выполняемая через SSH, возвращает код выхода 0, даже если в скрипте явно указано «exit 5», а также предложим решение данной проблемы.

1. Понимание проблемы

Когда вы выполняете команду через SSH, возникает ряд нюансов, которые влияют на возвращаемый код выхода. Рассмотрим следующее:

ssh host sh -c 'exit 5' && echo OK || echo FAIL

На первый взгляд, вышеуказанная команда должна вернуть «FAIL», поскольку она должна завершаться с кодом выхода 5. Однако вывод будет «OK». Важно понять, что:

  • При выполнении команды ssh удалённый процесс получает на вход строку, а не список аргументов, как это происходит в интерактивной оболочке.
  • Команда sh -c 'exit 5' будет интерпретирована как sh -c exit 5, и 5 будет интерпретироваться как аргумент, а не как часть команды exit.

Эта ошибка также заметна, если вы попытаетесь выполнить команду с дополнительными аргументами:

ssh host sh -c 'exit 5' a b c

В этом случае 5 будет передано как второй аргумент, что снова приведет к неверному коду выхода.

2. Правильное использование кавычек

Чтобы корректно передать код выхода в удалённую оболочку, необходимо правильно использовать кавычки:

ssh host "sh -c 'exit 5'" && echo OK || echo FAIL

Или можно использовать экранирование:

ssh host sh -c "'exit 5'" && echo OK || echo FAIL

Также можно использовать двойные кавычки, чтобы объединить команду в единую строку:

ssh host "sh -c \"exit 5\"" && echo OK || echo FAIL

Теперь удалённый процесс будет корректно получать команду exit 5 и завершаться с необходимым кодом.

3. Усложненный сценарий с подскриптами

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

Пример скрипта:

#!/bin/bash
sub-process-that-fails
rc=$?
echo $rc
exit $rc

Чтобы правильно запустить этот скрипт и получить корректный код выхода, используйте следующую команду:

ssh -t -t host "/bin/bash -x /srv/scripts/run.sh" ; echo $?

Если вы столкнулись с дополнительными проблемами, проверьте, не установлены ли ловушки (trap), которые могут изменять код выхода. Например, наличие команды trap 'kill 0' EXIT в вашем .bashrc может привести к ошибкам, так как она завершает все подпроцессы, что также приводит к возвращению кода 0.

Заключение

Используйте правильные кавычки и экранирование для передачи команд через SSH, чтобы убедиться, что код выхода передается корректно. При работе со скриптами и подскриптами следите за тем, чтобы код выхода не играл первую скрипту, и избегайте ловушек, которые могут повлиять на финальный код завершения.

Внимательность к деталям в ваших командах позволит устранить такие проблемы на ранних стадиях и обеспечит надежное выполнение ваших автоматизированных процессов.

Оцените материал
Добавить комментарий

Капча загружается...