Передавать команды в качестве входных данных для другой команды (su, ssh, sh и т. Д.)

У меня есть сценарий, в котором мне нужно запустить команду, а затем передать некоторые дополнительные команды в качестве команд для этой команды. Я старался

su
echo I should be root now:
who am I
exit
echo done.

... но это не работает: su удается, но тогда командная строка просто смотрит на меня. Если я наберу exit по подсказке echo а также who am i и т. д. начать выполнение! И echo done. не выполняется на всех.

Точно так же мне нужно, чтобы это работало над ssh:

ssh remotehost
# this should run under my account on remotehost
su
## this should run as root on remotehost
whoami
exit
## back
exit
# back

Как мне это решить?

Я ищу ответы, которые решают это в общем, и которые не являются специфичными для su или же ssh особенно. Цель состоит в том, чтобы этот вопрос стал каноническим для этой конкретной модели.

3 ответа

Решение

Сценарий оболочки - это последовательность команд. Оболочка прочитает файл сценария и выполнит эти команды одну за другой.

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

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

Теперь при выполнении скрипта стандартный ввод остается терминалом (если вы не использовали перенаправление), но команды читаются из файла скрипта, а не из стандартного ввода. (Противоположность была бы очень громоздкой - любая read будет использовать следующую строку сценария, cat будет отбрасывать всю остальную часть сценария, и не будет никакого способа с ним взаимодействовать!) Файл сценария содержит только команды для экземпляра оболочки, который его выполняет (хотя, конечно, вы все равно можете использовать здесь документ и т. д. для встраивания входных данных как командные аргументы).

Другими словами, эти "неправильно понятые" команды (su, ssh, sh, sudo, bash и т. д.) когда запускается один (без аргументов), запускается интерактивная оболочка, а в интерактивном сеансе это, очевидно, нормально; но когда запускается из скрипта, это очень часто не то, что вы хотите.

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

su root who am i
ssh user@remote uname -a
sh -c 'who am i; echo success'

Многие из этих команд также будут принимать команды на стандартный ввод:

printf 'uname -a; who am i; uptime' | ssh user@remote
printf 'uname -a; who am i; uptime' | sh

что также удобно позволяет использовать здесь документы:

ssh user@remote <<'____HERE'
    uname -a
    who am i
    uptime
____HERE

sh <<'____HERE'
    uname -a
    who am i
    uptime
____HERE

Для команд, которые принимают один аргумент команды, эта команда может быть sh или же bash с несколькими командами:

sudo sh -c 'uname -a; who am i; uptime'

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

Добавление к tripleee:

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

Если этот блок вашего скрипта содержит раскрытие параметров, подстановку команд и / или арифметическое расширение, то вы должны использовать средство "здесь-документ" оболочки немного по-разному, в зависимости от того, где вы хотите, чтобы эти расширения выполнялись.

1. Все расширения должны выполняться в пределах родительской оболочки.

Тогда разделитель здесь документа должен быть без кавычек.

command <<DELIMITER
...
DELIMITER

Пример:

#!/bin/bash

a=0
mylogin=$(whoami)
sudo sh <<END
    a=1
    mylogin=$(whoami)
    echo a=$a
    echo mylogin=$mylogin
END
echo a=$a
echo mylogin=$mylogin

Выход:

a=0
mylogin=leon
a=0
mylogin=leon

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

Затем должен быть указан разделитель этого документа.

command <<'DELIMITER'
...
DELIMITER

Пример:

#!/bin/bash

a=0
mylogin=$(whoami)
sudo sh <<'END'
    a=1
    mylogin=$(whoami)
    echo a=$a
    echo mylogin=$mylogin
END
echo a=$a
echo mylogin=$mylogin

Выход:

a=1
mylogin=root
a=0
mylogin=leon

3. Некоторые расширения должны выполняться в дочерней оболочке, некоторые - в родительской.

Затем разделитель документа here должен быть заключен в кавычки, и вы должны экранировать те выражения расширения, которые должны выполняться в дочерней оболочке.

Пример:

#!/bin/bash

a=0
mylogin=$(whoami)
sudo sh <<END
    a=1
    mylogin=\$(whoami)
    echo a=$a
    echo mylogin=\$mylogin
END
echo a=$a
echo mylogin=$mylogin

Выход:

a=0
mylogin=root
a=0
mylogin=leon

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

Выписка из справочной страницы:

Expect это программа, которая "общается" с другими интерактивными программами по сценарию. Следуя сценарию, Expect знает, чего можно ожидать от программы и какой должен быть правильный ответ. Интерпретируемый язык обеспечивает ветвящиеся и высокоуровневые структуры управления для управления диалогом. Кроме того, пользователь может взять на себя управление и взаимодействовать напрямую при желании, а затем вернуть управление сценарию.

Вот рабочий пример использования expect:

set timeout 60

spawn sudo su -

expect "*?assword" { send "*secretpassword*\r" }
send_user "I should be root now:"

expect "#" { send "whoami\r" }
expect "#" { send "exit\r" }
send_user "Done.\n"
exit

Затем скрипт можно запустить с помощью простой команды:

$ expect -f custom.script

Вы можете просмотреть полный пример на следующей странице: http://www.journaldev.com/1405/expect-script-example-for-ssh-and-su-login-and-running-commands

Примечание. Ответ, предложенный @tripleee, будет работать только в том случае, если стандартный ввод можно прочитать один раз в начале команды или если был выделен tty, и не будет работать для любой интерактивной программы.

Пример ошибок, если вы используете трубу

echo "su whoami" |ssh remotehost
--> su: must be run from a terminal

echo "sudo whoami" |ssh remotehost
--> sudo: no tty present and no askpass program specified

В SSH вы можете принудительно выделить TTY с несколькими -t параметры, но когда sudo спросит пароль, не получится.

Без использования такой программы, как expect любой вызов функции / программы, которая может получить информацию от stdin, приведет к сбою следующей команды:

ssh use@host <<'____HERE'
  echo "Enter your name:"
  read name
  echo "ok."
____HERE
--> The `echo "ok."` string will be passed to the "read" command
Другие вопросы по тегам