Как выполнить сложную командную строку через SSH?

Как выполнить сложную командную строку через SSH?

Иногда мне нужно получить env контейнера docker, запущенного на удаленном хосте. Для этого я вхожу на хост:

ssh [email protected]

и затем я запускаю эту команду:

sudo docker exec -it `sudo docker ps | grep mycontainername | awk '{print $1;}'` env

Теперь я хочу сделать это одной командой (делать это более 3 раз и автоматизировать это... :-) ).

Это работает:

 ssh -t [email protected] sudo docker ps | grep mycontainername

но когда я это делаю

ssh -t [email protected] sudo docker exec -it `sudo docker ps | grep mycontainername | awk '{print $1;}'` env

я получил

"docker exec" requires at least 2 arguments.
See 'docker exec --help'.

Usage:  docker exec [OPTIONS] CONTAINER COMMAND [ARG...]

Run a command in a running container
Connection to 123.456.789.10 closed.

Кто-нибудь знает, как это запустить?

решение1

Общие замечания

В Bash есть несколько ключевых слов, которые влияют на разбор того, что следует за ними, например [[. Но sshэто не одно из них, это обычная команда. Это означает, что:

  • Вся ssh …строка обычно анализируется вашимместныйshell; символы вроде |, ;, *, "или $пробел что-то значат для shell, они не попадут в ssh, если вы не заключите их в кавычки или не экранируете (за редкими исключениями, например, sole $как отдельное слово не является специальным). Это первый уровень разбора и интерпретации.

  • Какие бы аргументы ни попадали ssh(или любая другая обычная команда) после того, как оболочка выполнила свою работу, они являются просто аргументами, строками. Теперь задача инструмента — интерпретировать их. Это второй уровень.

В случае sshнекоторых (ноль, один или более) аргументов командной строки интерпретируются как команда, которая должна быть запущена на стороне сервера. В общем случае sshможет построить команду из многих аргументов. Эффект такой, как если бы вы вызвали что-то вроде этого на сервере:

"$SHELL" -c "$command_line_built_by_ssh"

(Я не утверждаю, что это так)точно(Как это, но это, безусловно, достаточно близко, чтобы понять, что происходит. Я написал это так, как будто это вызывается в оболочке, поэтому это выглядит знакомым; но на самом деле оболочки пока нет. И нет $command_line…переменной, я просто использую это имя, чтобы сослаться на некоторую строку для цели этого ответа.)

Затем $SHELLна сервере парсится $command_line…самостоятельно. Это третий уровень.


Конкретные наблюдения

Команда, которая не удалась

ssh -t [email protected] sudo docker exec -it `sudo docker ps | grep mycontainername | awk '{print $1;}'` env

не удалось, так как 123.456.789.10это недопустимый IP-адрес.

Хорошо, я понимаю, 123.456.789.10что это заполнитель, но он все равно недействителен. :)

Команда не выполнена, поскольку она была выполнена sudo docker ps | grep mycontainername | awk '{print $1;}'локально. Вывод, вероятно, был пуст. Тогда $command_line_built_by_sshэто было далеко не то, что вы хотели.

Примечание: ssh … | grep mycontainernameзапустите grepлокально (вы можете знать об этом, а можете и не знать).


Обсуждение

Чтобы контролировать то, что получит удаленная оболочка $command_line_built_by_ssh, вам нужно понимать, предсказывать и руководить разбором и интерпретацией, которые происходят до этого. Вам нужно создать свою локальную команду, поэтомупослелокальную оболочку и sshпереваривает ее, она становится именно тем, что $command_line…вы хотите выполнить на удаленной стороне.

Это может быть довольно сложно, если вы действительно хотите, чтобы ваша локальная оболочка расширялась или заменяла что-либо до того, как результат станет ssh. Ваш случай проще, поскольку у вас уже есть дословная строка, которую вы хотите видеть как $command_line_built_by_ssh. Строка такая:

sudo docker exec -it $(sudo docker ps | grep mycontainername | awk '{print $1;}') env

Примечания:

  • Я использовал подстановку команд в форме $(), а не обратных кавычек. Естьпричины предпочесть$().
  • dockerЯ вообще не знаю , не могу сказать $(…), нужно ли брать двойные кавычки. В общемне цитировать почти всегда плохо. Спросите себя, что происходит, когда подстановка возвращает несколько слов (т. е. несколько строк вводят awk). Это другая проблема (если вообще проблема в этом случае), и я не буду ее рассматривать в этом ответе.

Чтобы защитить все от расширения/интерпретации локальной оболочкой, вам нужно правильно заключить в кавычки или экранировать (с помощью \) все символы, которые могут вызвать расширение или могут быть интерпретированы. В этом случае заключите в кавычки $, (, ), |, ;, {, }, 'и (возможно или необязательно) пробелы.

Я сказал "возможно или по желанию кавычки или экранирование пробелов" из-за того, как это ssh … some commandработает. Если он находит два или более аргументов, которые он интерпретирует как код для запуска на сервере, он объединяет их, добавляя одиночные пробелы между ними. Вот как это $command_line_built_by_sshпостроено. Если вы не заключаете в кавычки и не экранируете пробелы в том, что выглядит как код для удаленной оболочки, то локальная оболочка будет использовать пробелы (и табуляции) при разделении слов, а затем sshдобавит пробелы. Результат может быть не совсем тем, что вы хотите, если есть табуляции или несколько последовательных пробелов. Например:

ssh user@server echo a     b

sshполучает user@server, echo, a, b. Удаленная команда будет echo a bи echoтам будет получить a, b. Она напечатает a b.

Тогда это:

ssh user@server 'echo a     b'

sshполучает user@server, echo a b. Удаленная команда будет echo a bи echoтам будет получить a, b. Она напечатает a b.

И наконец это:

ssh user@server 'echo "a     b"'

sshполучает user@server, echo "a b". Удаленная команда будет echo "a b"и echoтам будет получить a b. Она напечатает a b.

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


Решение

Собирая всю эту информацию вместе (и все еще предполагая, что вы хотите защититьвсеот расширения/интерпретации локальной оболочкой), я советую следующее:

  • Цитата или побег.
  • Предпочитатьцитированиепо экранированию, потому что одна пара кавычек может защитить много символов, в то время как одна \защищает только один символ. Скорее всего, вам понадобится много обратных косых черт, чтобы защитить все; часто того же результата можно достичь с помощью всего одной пары кавычек.
  • Предпочитатьодинарные кавычки( '), они могут защитить все, кроме '. С другой стороны, двойные кавычки ( ") могут защитить ', но не $ни "(ни \иногда, ни !иногда в Bash), если только $и такие не экранированы (т.е. экранированыизакавычен, т.е. экранирован в двойных кавычках; за исключением! который хлопотный).
  • Предпочитаю предоставлять команду(ы) какодинокийаргумент в пользу ssh.

Это приводит к следующей процедуре:

  1. Подготовьте дословную команду, которую вы хотите выполнить на удаленной стороне.
  2. Замените каждый 'на '"'"'или на '\''(вы можете выбрать независимо для каждого ').
  3. Заключите всю полученную строку в одинарные кавычки.
  4. Добавить ssh …спереди.

Ваша дословная команда:

sudo docker exec -it $(sudo docker ps | grep mycontainername | awk '{print $1;}') env

Результатом процедуры является:

ssh -t user@server 'sudo docker exec -it $(sudo docker ps | grep mycontainername | awk '\''{print $1;}'\'') env'
# single-quoted     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^    ^^^^^^^^^^^    ^^^^^
# escaped                                                                                ^              ^

Обратите внимание, что процедура может привести или не привести к максимально короткой строке. С пониманием иногда можно «оптимизировать» строку. Но процедура довольнопростойи полностью надежный. Если вы просто знаете, что хотите защитить все от расширения/интерпретации локальной оболочкой, сама процедура не требует никаких дополнительных знаний.


Автоматизация

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

  1. В локальной оболочке определите следующую пользовательскую функцию и привязку:

     _prepend_ssh() { READLINE_LINE="ssh  ${READLINE_LINE@Q}"; READLINE_POINT=4; }
     bind -x '"\C-x\C-h":_prepend_ssh'
    
  2. Оставаясь в локальной оболочке, введите (или вставьте) команду, которую вы хотите запустить в удаленной оболочке; не выполняйте. Команда должна бытьточнокак вы хотите, чтобы это было в удаленной оболочке.

  3. Нажмите Ctrl+ x, Ctrl+ h. Локальная оболочка позаботится о кавычках. Она также добавит sshперед и поместит курсор сразу после.

  4. Добавьте (введите) недостающие аргументы (например -t user@server). Для вашего удобства курсор уже находится в нужном положении, чтобы сделать это.

  5. Enter


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

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

echo "$PATH"; date

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

ssh user@server 'echo "$PATH"; date'

Пример простой, но в целом добавление кавычек не всегда так просто. В качестве альтернативы мы можем передать команду по конвейеру следующим образом (первый echoдля простоты;printfлучше):

echo 'echo "$PATH"; date' | ssh user@server bash

что все еще требует эти одинарные кавычки. Но если у вас есть команда(ы) в файле, то:

<file ssh user@server bash

Или даже без файла (здесь документ):

ssh user@server bash <<'EOF'
echo "$PATH"
date
EOF

(Обратите внимание на кавычки, чтобы <<'EOF'предотвратить $PATHлокальное расширение.)

Преимущества:

  • Вы можете легко передавать многострочные команды/фрагменты/скрипты (я разделил их echo … ; dateтолько для того, чтобы показать это).
  • Никакого дополнительного уровня цитирования не требуется.
  • Вы можете явно выбрать удаленный интерпретатор, который не обязательно должен быть оболочкой (например , bashили zsh, или python).

Недостатки:

  • Вам следует явно указать удаленного интерпретатора, в противном случае будет использоваться интерпретатор по умолчанию.авторизоватьсяБудет запущена оболочка, возможно, будет напечатано сообщение дня. Вы по-прежнему можете использовать оболочку по умолчанию в качестве оболочки без входа, указав правильно кавычки exec "$SHELL"(строка будет выглядеть как ssh … 'exec "$SHELL"' <<'EOF').
  • Стандартный ввод sshне является терминалом, поэтому вы не можете использовать -t(именно поэтому я не использовал вашу исходную команду в качестве примера).
  • Команды попадают на удаленный интерпретатор ( bashв примере) через его стандартный ввод. Возможные проблемы:
    • Дочерние процессы (или встроенные) будут использовать тот же stdin. Если какой-либо из них читает из своего stdin, то он будет читать тот же поток, возможно, он будет читать следующую команду(ы), предназначенную для интерпретатора. Это поведение можно подавить или даже творчески (зло)использовать, но я не буду вдаваться в подробности.
    • Вы не сможете легко использовать этот канал для передачи чего-либо еще.

Связанный контент