Я пытаюсь написать оболочку script, которая создает некоторые каталоги на удаленном сервере, а затем использует scp для копирования файлов с моей локальной машины на пульт. Вот что я до сих пор:
ssh -t user@server<<EOT
DEP_ROOT='/home/matthewr/releases'
datestamp=$(date +%Y%m%d%H%M%S)
REL_DIR=$DEP_ROOT"/"$datestamp
if [ ! -d "$DEP_ROOT" ]; then
echo "creating the root directory"
mkdir $DEP_ROOT
fi
mkdir $REL_DIR
exit
EOT
scp ./dir1 user@server:$REL_DIR
scp ./dir2 user@server:$REL_DIR
Всякий раз, когда я запускаю его, я получаю это сообщение:
Pseudo-terminal will not be allocated because stdin is not a terminal.
И script просто висит навсегда.
Мой открытый ключ доверен на сервере, и я могу полностью запустить все команды за пределами script. Любые идеи?
Попробуйте ssh -t -t
(или ssh -tt
для краткости), чтобы принудительно присвоить псевдо-tty, даже если stdin не является терминалом.
См. также: Завершение сеанса SSH, выполненного с помощью bash script
Из ssh manpage:
-T Disable pseudo-tty allocation.
-t Force pseudo-tty allocation. This can be used to execute arbitrary
screen-based programs on a remote machine, which can be very useful,
e.g. when implementing menu services. Multiple -t options force tty
allocation, even if ssh has no local tty.
Также с опцией -T
из руководства
Отключить псевдо -T ty распределение
Per zanco answer, вы не предоставляете удаленную команду ssh
, учитывая, как оболочка анализирует командную строку. Чтобы решить эту проблему, измените синтаксис вашего вызова команды ssh
, чтобы удаленная команда состояла из синтаксически правильной многострочной строки.
Существует множество синтаксисов, которые можно использовать. Например, поскольку команды могут быть отправлены в bash
и sh
и, возможно, другие оболочки тоже, самое простое решение состоит в том, чтобы просто комбинировать вызов ssh
оболочки с heredocs:
ssh user@server /bin/bash <<'EOT'
echo "These commands will be run on: $( uname -a )"
echo "They are executed by: $( whoami )"
EOT
Обратите внимание, что выполнение вышеуказанного без /bin/bash
приведет к предупреждению Pseudo-terminal will not be allocated because stdin is not a terminal
. Также обратите внимание, что EOT
окружен одинарными кавычками, так что bash
распознает heredoc как nowdoc, отключив интерполяцию локальных переменных, чтобы текст команды был передан как есть - ssh
.
Если вы являетесь поклонником труб, вы можете переписать вышеприведенное значение следующим образом:
cat <<'EOT' | ssh user@server /bin/bash
echo "These commands will be run on: $( uname -a )"
echo "They are executed by: $( whoami )"
EOT
То же предостережение о /bin/bash
относится к указанному выше.
Еще один действительный подход - передать многострочную удаленную команду как одну строку, используя несколько уровней интерполяции переменных bash
следующим образом:
ssh user@server "$( cat <<'EOT'
echo "These commands will be run on: $( uname -a )"
echo "They are executed by: $( whoami )"
EOT
)"
Решение выше устраняет эту проблему следующим образом:
ssh user@server
анализируется bash и интерпретируется как команда ssh
, за которым следует аргумент user@server
, который должен быть передан команде ssh
"
начинается интерполированная строка, которая при завершении будет содержать аргумент, который будет передан команде ssh
, которая в этом случае будет интерпретироваться ssh
как удаленная команда для выполнения как user@server
$(
начинает команду, которая должна быть выполнена, при этом вывод захватывается окруженной интерполированной строкой
cat
- это команда для вывода содержимого любого файла. Вывод cat
будет передан обратно в захваченную интерполированную строку
<<
начинается bash heredoc
'EOT'
указывает, что имя heredoc равно EOT. Одиночные кавычки '
, окружающие EOT, указывают, что heredoc следует анализировать как nowdoc, что является специальной формой heredoc, в которой содержимое не получает интерполяцию с помощью bash, а скорее передается в буквальном формате
Любое содержимое, которое встречается между <<'EOT'
и <newline>EOT<newline>
, будет добавлено к выходу nowdoc
EOT
завершает работу nowdoc, в результате создается временный файл nowdoc и передается обратно в вызывающую команду cat
. cat
выводит nowdoc и передает выход обратно в захваченную интерполированную строку
)
завершает выполнение команды
"
завершает захваченную интерполированную строку. Содержимое интерполированной строки будет передано обратно в ssh
как один аргумент командной строки, который ssh
будет интерпретировать как удаленную команду для выполнения как user@server
Если вам нужно избегать использования внешних инструментов, таких как cat
, и не против иметь два оператора вместо одного, используйте встроенный read
с heredoc для генерации команды SSH:
IFS='' read -r -d '' SSH_COMMAND <<'EOT'
echo "These commands will be run on: $( uname -a )"
echo "They are executed by: $( whoami )"
EOT
ssh user@server "${SSH_COMMAND}"
Я добавляю этот ответ, потому что он решил связанную с этим проблему с тем же сообщением об ошибке.
Проблема: я установил cygwin под Windows и получил эту ошибку: Pseudo-terminal will not be allocated because stdin is not a terminal
Разрешение. Оказывается, что у меня была не клиентская программа и утилиты openssh. Из-за этого cygwin использовал версию ssh для Windows, а не версию cygwin. Решение заключалось в установке пакета openssh cygwin.
$ which ssh/cygdrive/c/Program Files (x86)/Git/bin/ssh
openssh
это может быть путь для Windows: superuser.com/a/301026/260710
Предупреждающее сообщение Pseudo-terminal will not be allocated because stdin is not a terminal.
связано с тем, что для ssh
не указана команда, а stdin перенаправляется из документа.
Из-за отсутствия указанной команды в качестве аргумента ssh
сначала ожидает интерактивный сеанс входа (который потребует выделения pty на удаленном хосте), но затем должен понять, что его локальный stdin не является tty/pty. Перенаправление ssh
stdin из документа здесь обычно требует, чтобы команда (например, /bin/sh
) указывалась как аргумент ssh
- и в таком случае по умолчанию на удаленном хосте не будет назначено pty.
Поскольку для ssh
нет команд, которые требуют наличия tty/pty (например, vim
или top
), переключение -t
на ssh
является излишним.
Просто используйте ssh -T user@server <<EOT ...
или ssh user@server /bin/bash <<EOT ...
, и предупреждение исчезнет.
Если <<EOF
не является экранированным или однокасканным (i. e. <<\EOT
или <<'EOT'
), переменные внутри документа здесь будут разворачиваться локальной оболочкой до того, как она выполнит ssh ...
. Эффект заключается в том, что переменные внутри документа здесь остаются пустыми, поскольку они определены только в удаленной оболочке.
Итак, если $REL_DIR
должен быть доступен локальной оболочке и определен в удаленной оболочке, $REL_DIR
должен быть определен вне документа здесь перед командой ssh
(версия 1 > ниже); или, если используется <<\EOT
или <<'EOT'
, вывод команды ssh
может быть назначен REL_DIR
, если единственный вывод команды ssh
в stdout генерируется echo "$REL_DIR"
внутри экранированного/документ с одним кавычками ( версия 2).
Третий вариант заключается в том, чтобы сохранить этот документ в переменной, а затем передать эту переменную в качестве аргумента команды в ssh -t user@server "$heredoc"
(версия 3) ниже).
И последнее, но не менее важное: неплохо было бы проверить, были ли успешно созданы каталоги на удаленном хосте (см. проверить, существует ли файл на удаленном хосте с ssh).
# version 1
unset DEP_ROOT REL_DIR
DEP_ROOT='/tmp'
datestamp=$(date +%Y%m%d%H%M%S)
REL_DIR="${DEP_ROOT}/${datestamp}"
ssh localhost /bin/bash <<EOF
if [ ! -d "$DEP_ROOT" ] && [ ! -e "$DEP_ROOT" ]; then
echo "creating the root directory" 1>&2
mkdir "$DEP_ROOT"
fi
mkdir "$REL_DIR"
#echo "$REL_DIR"
exit
EOF
scp -r ./dir1 user@server:"$REL_DIR"
scp -r ./dir2 user@server:"$REL_DIR"
# version 2
REL_DIR="$(
ssh localhost /bin/bash <<\EOF
DEP_ROOT='/tmp'
datestamp=$(date +%Y%m%d%H%M%S)
REL_DIR="${DEP_ROOT}/${datestamp}"
if [ ! -d "$DEP_ROOT" ] && [ ! -e "$DEP_ROOT" ]; then
echo "creating the root directory" 1>&2
mkdir "$DEP_ROOT"
fi
mkdir "$REL_DIR"
echo "$REL_DIR"
exit
EOF
)"
scp -r ./dir1 user@server:"$REL_DIR"
scp -r ./dir2 user@server:"$REL_DIR"
# version 3
heredoc="$(cat <<'EOF'
# -onlcr: prevent the terminal from converting bare line feeds to carriage return/line feed pairs
stty -echo -onlcr
DEP_ROOT='/tmp'
datestamp="$(date +%Y%m%d%H%M%S)"
REL_DIR="${DEP_ROOT}/${datestamp}"
if [ ! -d "$DEP_ROOT" ] && [ ! -e "$DEP_ROOT" ]; then
echo "creating the root directory" 1>&2
mkdir "$DEP_ROOT"
fi
mkdir "$REL_DIR"
echo "$REL_DIR"
stty echo onlcr
exit
EOF
)"
REL_DIR="$(ssh -t localhost "$heredoc")"
scp -r ./dir1 user@server:"$REL_DIR"
scp -r ./dir2 user@server:"$REL_DIR"
Я не знаю, откуда происходит зависание, но перенаправление (или трубопровод) команд в интерактивный ssh - это, в общем, рецепт проблем. Более разумно использовать стиль командной строки для запуска в качестве последнего аргумента и передать script в командной строке ssh:
ssh user@server 'DEP_ROOT="/home/matthewr/releases"
datestamp=$(date +%Y%m%d%H%M%S)
REL_DIR=$DEP_ROOT"/"$datestamp
if [ ! -d "$DEP_ROOT" ]; then
echo "creating the root directory"
mkdir $DEP_ROOT
fi
mkdir $REL_DIR'
(Все в одном гигантском аргументе командной строки '
-delimited).
Сообщение псевдотерминала из-за вашего -t
, которое просит ssh попытаться сделать среду, на которой он запущен на удаленной машине, выглядит как фактический терминал для программ, которые там работают. Ваш ssh-клиент отказывается это делать, потому что его собственный стандартный вход не является терминалом, поэтому он не имеет возможности передавать специальные интерфейсные API-интерфейсы с удаленного компьютера на ваш фактический терминал в локальном конце.
Что вы пытались достичь с помощью -t
в любом случае?
Вся необходимая информация содержится в существующих ответах, но позвольте мне попробовать прагматическое резюме:
TL;DR:
ПРОВЕРИТЬ команды для запуска с использованием аргумента командной строки: ssh jdoe@server '...'
'...'
строки могут охватывать несколько строк, поэтому вы можете сохранить свой код читаемым даже без использования здесь-документа: ssh jdoe@server '
...
'
НЕ передавайте команды через stdin, как в случае, когда вы используете here-document: ssh jdoe@server <<'EOF' # Do NOT do this
...
EOF
Передача команд в качестве аргумента работает как-is и:
exit
в конце ваших команд, потому что сеанс будет автоматически завершен после обработки команд.Короче: передача команд через stdin - это механизм, который не соответствует дизайну ssh
и вызывает проблемы, которые затем должны быть обработаны.
Продолжайте читать, если хотите узнать больше.
ssh
механизм принятия команд для выполнения на целевом сервере является аргументом командной строки: конечный операнд (необязательный аргумент) принимает строку, содержащую одну или несколько команд оболочки.
По умолчанию эти команды запускаются без присмотра в неинтерактивной оболочке без использования (псевдо) терминала (подразумевается опция -T
), и сеанс автоматически завершается, когда последняя команда завершает обработку.
Если ваши команды требуют взаимодействия с пользователем, например, для ответа на интерактивное приглашение, вы можете явно запросить создание pty (псевдо -tty), псевдотерминал, который позволяет взаимодействовать с удаленным сеансом, используя параметр -T
; например:.
ssh -t jdoe@server 'read -p "Enter something: "; echo "Entered: [$REPLY]"'
Обратите внимание, что интерактивная подсказка read
работает корректно с помощью pty, поэтому требуется опция -T
.
Использование pty имеет заметный побочный эффект: stdout и stderr объединены и оба передаются через stdout; другими словами: вы теряете различие между регулярным и выходным сигналом ошибки; например:
ssh jdoe@server 'echo out; echo err >&2' # OK - stdout and stderr separate
ssh -t jdoe@server 'echo out; echo err >&2' # !! stdout + stderr -> stdout
В отсутствие этого аргумента ssh
создает интерактивную оболочку - в том числе, когда вы отправляете команды через stdin, в котором начинается проблема:
Для интерактивной оболочки ssh
по умолчанию обычно назначает pty (псевдотерминал), за исключением того, что его stdin не подключен к (реальному) терминалу.
Отправка команд через stdin означает, что ssh
stdin больше не подключен к терминалу, поэтому нет pty, и ssh
предупреждает вас соответственно: Pseudo-terminal will not be allocated because stdin is not a terminal.
Даже вариант -T
, чья явная цель - запросить создание pty, в этом случае не достаточно: вы получите то же предупреждение.
Несколько любопытно, что вы должны удвоить опцию -T
, чтобы заставить создание pty: ssh -t -t ...
или ssh -tt ...
показать, что вы действительно это действительно имеете в виду.
Возможно, обоснование необходимости этого очень преднамеренного шага состоит в том, что вещи могут работать не так, как ожидалось. Например, на macOS 10.12 кажущийся эквивалент указанной выше команды, предоставляющей команды через stdin и используя -tt
, не работает должным образом; сеанс застревает после ответа на приглашение read
: ssh -tt jdoe@server <<<'read -p "Enter something: "; echo "Entered: [$REPLY]"'
В маловероятном случае, когда команды, которые вы хотите передать в качестве аргумента, слишком длинны для вашей системы (если ее длина приближается к getconf ARG_MAX
- см. в этой статье), сначала скопируйте код в удаленную систему в виде script (используя, например, scp
), а затем отправьте команду для выполнения этого script.
В крайнем случае используйте -T
и предоставьте команды через stdin с помощью команды exit
, но обратите внимание, что если вам также нужны интерактивные функции, использование -tt
вместо -T
может не работать.
После прочтения многих этих ответов я решил поделиться своим решением. Все, что я добавил, это /bin/bash
перед heredoc, и он больше не дает ошибку.
Используйте это:
ssh user@machine /bin/bash <<'ENDSSH'
hostname
ENDSSH
Вместо этого (дает ошибку):
ssh user@machine <<'ENDSSH'
hostname
ENDSSH
Или используйте это:
ssh user@machine /bin/bash < run-command.sh
Вместо этого (дает ошибку):
ssh user@machine < run-command.sh
EXTRA
Если вам по-прежнему требуется удаленное интерактивное приглашение, например. если script вы запускаете удаленно, вы запрашиваете пароль или другую информацию, потому что предыдущие решения не позволят вам вводить подсказки.
ssh -t user@machine "$(<run-command.sh)"
И если вы также хотите зарегистрировать весь сеанс в файле logfile.log
:
ssh -t user@machine "$(<run-command.sh)" | tee -a logfile.log
У меня была такая же проблема в cygwin. Установка openssh в cygwin решает проблему лучше всего. Запуск на Windows ssh -t -t печатает текущую команду (перед stdout), которая мне не нравится.
У меня была такая же ошибка в Windows, используя emacs 24.5.1 для подключения к некоторым серверам компании через /ssh: user @host. Я решил, что проблема заключается в установке переменной "tramp-default-method" для "plink", и всякий раз, когда я подключаюсь к серверу, я опускаю протокол ssh. Для этого вам необходимо установить PuTTY plink.exe.
Решение
ssh -t foobar @localhost yourscript.pl
-t
, но в их конкретном сценарии этого недостаточно, что побудило начать вопрос.
ssh user@server /bin/bash <<EOT…