Псевдо-терминал не будет выделен, потому что stdin не является терминалом - PullRequest
291 голосов
/ 19 августа 2011

Я пытаюсь написать сценарий оболочки, который создает некоторые каталоги на удаленном сервере, а затем использует 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.

И скрипт просто висит навсегда.

Мой открытый ключдоверенный на сервере, и я могу выполнить все команды вне сценария просто отлично.Есть идеи?

Ответы [ 10 ]

438 голосов
/ 19 августа 2011

Попробуйте ssh -t -t (или ssh -tt для краткости), чтобы форсировать псевдо-tty, даже если stdin не является терминалом.

См. Также: Завершение сеанса SSH, выполняемого сценарием bash

С 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.
157 голосов
/ 19 января 2014

Также с опцией -T из вручную

Отключить псевдо-tty распределение

76 голосов
/ 13 февраля 2014

За ответ zanco , вы не предоставляете удаленную команду для 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
)"

Приведенное выше решение устраняет эту проблему следующим образом:

  1. ssh user@server анализируется bash и интерпретируется как команда ssh, за которой следует аргумент user@server для передачи в команду ssh

  2. " начинает интерполированную строку, которая после завершения будет содержать аргумент, который будет передан команде ssh, которая в этом случае будет интерпретироваться ssh как удаленная команда для выполнения как user@server

  3. $( начинает выполнение команды с вывода, захваченного окружающей интерполированной строкой

  4. cat - это команда для вывода содержимого любого файла. Вывод cat будет передан обратно в интерполированную строку захвата

  5. << начинается удар heredoc

  6. 'EOT' указывает, что именем heredoc является EOT. Одинарные кавычки ', окружающие EOT, указывают, что heredoc должен быть проанализирован как nowdoc , который представляет собой особую форму heredoc, в которой содержимое не интерполируется bash, а передается в буквальном формате

  7. Любой контент, который встречается между <<'EOT' и <newline>EOT<newline>, будет добавлен к выводу nowdoc

  8. EOT завершает действие nowdoc, в результате чего создается временный файл nowdoc и передается обратно вызывающей команде cat. cat выводит nowdoc и передает вывод обратно в интерполированную строку захвата

  9. ) завершает выполнение команды

  10. " завершает ввод интерполированной строки. Содержимое интерполированной строки будет передано обратно в 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}"
54 голосов
/ 07 августа 2013

Я добавляю этот ответ, потому что он решил проблему, связанную с тем же сообщением об ошибке.

Проблема : я установил cygwin под Windows и получал эту ошибку: Pseudo-terminal will not be allocated because stdin is not a terminal

Разрешение : Оказывается, у меня not установлена ​​клиентская программа и утилиты openssh.Из-за этого cygwin использовал реализацию ssh для Windows, а не версию cygwin.Решением было установить пакет openssh cygwin.

30 голосов
/ 13 февраля 2014

Предупреждающее сообщение Pseudo-terminal will not be allocated because stdin is not a terminal. связано с тем, что для ssh не указана команда, в то время как stdin перенаправляется из документа здесь. Из-за отсутствия указанной команды в качестве аргумента ssh сначала ожидает интерактивный сеанс входа в систему (который потребует выделения pty на удаленном хосте), но затем должен понимать, что его локальный stdin не является tty / pty. Для перенаправления stdin ssh из этого документа обычно требуется указание команды (например, /bin/sh) в качестве аргумента для ssh - и в этом случае pty не будет выделяться на удаленном хосте по умолчанию.

Поскольку нет никаких команд, которые должны выполняться через ssh, которые требуют наличия tty / pty (например, vim или top), переключение -t на ssh является излишним. Просто используйте ssh -T user@server <<EOT ... или ssh user@server /bin/bash <<EOT ..., и предупреждение исчезнет.

Если <<EOF не экранирован или не заключены в одинарные кавычки (т. Е. <<\EOT или <<'EOT'), переменные внутри документа здесь будут расширены локальной оболочкой перед выполнением ssh .... В результате переменные внутри документа здесь останутся пустыми, поскольку они определены только в удаленной оболочке.

Итак, если $REL_DIR должен быть доступен как локальной оболочкой, так и определен в удаленной оболочке, $REL_DIR должен быть определен вне документа here перед командой ssh ( version 1 ). ниже); или, если используется <<\EOT или <<'EOT', вывод команды ssh может быть назначен на REL_DIR, если единственный вывод команды ssh для stdout генерируется echo "$REL_DIR" внутри экранированного / документ, заключенный в одну цитату ( версия 2 ниже).

Третий вариант - сохранить документ here в переменной, а затем передать эту переменную в качестве аргумента команды в 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"
24 голосов
/ 23 января 2017

Вся соответствующая информация есть в существующих ответах, но позвольте мне попробовать прагматическое резюме :

ТЛ; др:

  • Передайте команды для запуска, используя аргумент командной строки :
    ssh jdoe@server '...'

    • '...' строки могут занимать несколько строк, поэтому вы можете сохранять код читабельным даже без использования документа здесь:
      ssh jdoe@server ' ... '
  • НЕ передавайте команды через stdin , как в случае использования здесь-документа :
    ssh jdoe@server <<'EOF' # Do NOT do this ... EOF

Передача команд в качестве аргумента работает как есть, и:

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

Вкратце: передача команд через stdin - это механизм, который расходится с дизайном ssh и вызывает проблемы, которые затем необходимо обойти.
Читайте дальше, если вы хотите узнать больше.


Дополнительная справочная информация:

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

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

  • Если ваши команды требуют взаимодействия с пользователем , например, при ответе на интерактивное приглашение, вы можете явно запросить создание pty (псевдо-tty) псевдо-терминал, который позволяет взаимодействовать с удаленным сеансом, используя опцию -t; e.g.:

    • 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 означает, что stdin ssh больше не подключен к терминалу, поэтому no pty создано и ssh warn ты соответственно :
      Pseudo-terminal will not be allocated because stdin is not a terminal.

    • Даже опция -t, чье явное назначение запрос создания pty, не достаточно в этом случае *: вы получите то же предупреждение.

      • Как ни странно, вы должны double опция -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 - см. в этой статье ), попробуйте скопировать код сначала в удаленную систему в форме сценария (используя, например, scp), а затем отправьте команду для выполнения этого сценария.

В крайнем случае, используйте -T и предоставьте команды с помощью stdin , с завершающей командой exit, но учтите, что если вам также нужны интерактивные функции, используйте -tt вместо -T может не работать.

21 голосов
/ 19 августа 2011

Я не знаю, откуда происходит зависание, но перенаправление (или передача) команд в интерактивный ssh ​​- это вообще рецепт проблем. Более надежно использовать стиль «команда для запуска как последний аргумент» и передать сценарий в командной строке 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'

(Все в одном гигантском многострочном аргументе командной строки ').

Псевдо-терминальное сообщение из-за вашего -t, который просит ssh попытаться сделать среду, которую он запускает на удаленной машине, похожей на реальный терминал для программ, которые там работают. Ваш ssh-клиент отказывается это делать, потому что его собственный стандартный ввод не является терминалом, поэтому он не может передавать специальные API-интерфейсы терминала с удаленного компьютера на ваш фактический терминал на локальном конце.

Чего вы пытались достичь с помощью -t? 1013 *

5 голосов
/ 07 июля 2016

Прочитав многие из этих ответов, я решил поделиться своим решением.Все, что я добавил, это /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 :

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

ssh -t user@machine "$(<run-command.sh)"

И если вы также хотите записать весь сеанс в файл logfile.log:

ssh -t user@machine "$(<run-command.sh)" | tee -a logfile.log
0 голосов
/ 14 декабря 2015

У меня была такая же ошибка под Windows, когда emacs 24.5.1 подключался к некоторым серверам компании через / ssh: user @ host. Что решило мою проблему, так это установив переменную «tramp-default-method» в «plink», и всякий раз, когда я соединяюсь с сервером, я пропускаю протокол ssh. Чтобы это работало, вам нужно установить plink.exe в PuTTY.

Решение

  1. M-x customize-variable (а затем нажмите Enter)
  2. tramp-default-method (и затем снова нажать Enter)
  3. В текстовое поле положить plink, а затем Применить и сохранить буфер
  4. Всякий раз, когда я пытаюсь получить доступ к удаленному серверу, я теперь использую C-x-f / user @ host: и затем вводю пароль. Теперь в Emacs в Windows правильно установлено соединение с моим удаленным сервером.
0 голосов
/ 20 ноября 2013

ssh -t foobar @ localhost yourscript.pl

...