Передавайте команды как входные данные в другую команду (su, ssh, sh и т.д.).

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

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. Цель этого вопроса - canonical для этого конкретного шаблона.

Ответ 1

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

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

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

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

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

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

su root -c 'who am i'
ssh [email protected] uname -a
sh -c 'who am i; echo success'

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

printf 'uname -a; who am i; uptime' | su
printf 'uname -a; who am i; uptime' | ssh [email protected]
printf 'uname -a; who am i; uptime' | sh

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

ssh [email protected] <<'____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 потому что команда завершится в любом случае, когда она выполнит сценарий (последовательность команд), который вы передали для выполнения.

Ответ 2

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

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

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

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. Некоторые расширения должны выполняться в дочерней оболочке, а некоторые - в родительском.

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

Пример:

#!/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

Ответ 3

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

Извлеките страницу руководства:

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

Вот рабочий пример с использованием 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

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

$ 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 [email protected] <<'____HERE'
  echo "Enter your name:"
  read name
  echo "ok."
____HERE
--> The `echo "ok."` string will be passed to the "read" command