Псевдо-терминал не будет выделен, потому что stdin не является терминалом

345

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

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

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

Мэтью
источник
4
Вы можете просто указать терминал для использования, какssh user@server /bin/bash <<EOT…
Buzut
3
@Buzut: Вы, вероятно, имеете в виду оболочку , но да, указание /bin/bashявно является одним из способов избежать проблемы.
mklement0
1
@ mklement0 действительно, это то, что я имел в виду. Спасибо за исправление этого;)
Buzut

Ответы:

513

Попробуйте 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.
carok
источник
21
У меня похожая проблема в скрипте, который запускается здесь. Я добавил -t -t, но теперь получаю новую ошибку. "tcgetattr:
неподходящий
5
Почему ssh -t -tи нет ssh -tt? Есть ли разница, о которой я не знаю?
Джек,
3
@MasterZ То же самое и здесь. Было бы неплохо получить ответ на этот вопросInappropriate IOCtl for device
krb686
11
@ Джек -ttи -t -tэквивалентны; указание аргументов по отдельности или смешение вместе становится вопросом личного стиля / предпочтения, и когда дело доходит до этого, есть веские аргументы для того, чтобы сделать это в любом случае. но на самом деле это просто личные предпочтения.
JDS
5
Что это значит, когда он говорит "даже если у ssh нет локального tty"?
CMCDragonkai
192

Также с опцией -Tиз руководства

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

Эмиль Бойда
источник
11
Этот ответ нуждается в большем количестве баллов - это правильный ответ, и не слишком длинный, как ответ от zanco, абсурдно, что «о, в любом случае, выделите TTY с -t -t», получает 107 баллов, когда вы можете просто пропустить его вообще
nhed
2
Просто проголосовал; для меня это сработало лучше, чем -t -t
Винсент Хайбаррен
15
'-t -t' работает, но с '-T' я получаю 'sudo: извините, у вас должен быть tty для запуска sudo'
Иван Балашов
3
@nhed: эта -ttопция лучше всего подходит для тех из нас, кто действительно хочет TTY и получает сообщение об ошибке OP. Этот -Tответ лучше, если вам не нужен TTY.
ErichBSchulz
3
@nhed - конечно, - но я уверен, что другие, как я, пришли сюда от поиска ошибки в сообщении об ошибке. Похоже, не лишено смысла объяснять, как другие гуглеры должны выбирать между двумя конкурирующими ответами. а может и нет. я не знаю
ErichBSchulz
91

Согласно ответу 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. <<начинается Bash 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}"
Дежай Клэйтон
источник
+1 через полтора года! :) действительно четкое объяснение. хорошо сделано
yaroslavTir
1
Для меня (и я ни в коем случае не человек "DevOps") это то, что сработало (я использую Jenkins). Я также попробовал предложения "-t -t" и "-T", но столкнулся с проблемами открытого потока и неисполнением.
Райан Крюс
2 года спустя, действительно полезно
Джек-нью
+1 Потрясающее объяснение. Но если вы сделаете последний фрагмент кода (IFS = '' *) функцией для выполнения удаленных команд, как бы вы передали $ 1 в IFS = '' * *? Доцент, кажется, признает содержание в 1 доллар вообще.
Кристиан Матиас Амбук
1
@ mklement0 Конечно, вы можете экранировать строки по мере необходимости, но кому нужны хлопоты, связанные с необходимостью изменять операторы скрипта, которые вы можете просто копировать и вставлять из другого скрипта оболочки, или в этом случае stackoverflow? Кроме того, элегантность catрешения заключается в том, что оно выполняется в одном (хотя и составном) операторе и не приводит к временным переменным, загрязняющим среду оболочки.
Деджей Клейтон,
63

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

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

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

Эндрю Прок
источник
10
Для меня оказалось, что это была еще одна реализация SSH в PATH:$ which ssh/cygdrive/c/Program Files (x86)/Git/bin/ssh
FelixJongleur42
и для установки opensshэтого может быть способ пойти для Windows: superuser.com/a/301026/260710
Андреас Дитрих
Это решение работает для меня. После установки openssh проблема исчезла.
18:25
34

Это предупреждающее сообщение Pseudo-terminal will not be allocated because stdin is not a terminal.связано с тем, что команда не указана, в sshто время как стандартный ввод перенаправлен из документа здесь. Из-за отсутствия указанной команды в качестве аргумента sshсначала ожидается интерактивный сеанс входа в систему (который потребует выделения pty на удаленном хосте), но затем он должен понимать, что его локальный stdin не является tty / pty. Перенаправление sshstdin из документа здесь обычно требует указания команды (такой как /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') внутри документа here, он будет расширен локальной оболочкой перед выполнением ssh .... В результате переменные внутри документа here останутся пустыми, поскольку они определены только в удаленной оболочке.

Таким образом, если он $REL_DIRдолжен быть доступен как локальной оболочкой, так и определен в удаленной оболочке, $REL_DIRон должен быть определен вне документа here перед sshкомандой ( версия 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"
zanco
источник
5
Спасибо за очень подробное объяснение того, что означает эта ошибка и почему она появляется при использовании heredoc, это помогло мне понять ее, а не просто обойти ее.
Дан
29

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

ТЛ; др:

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

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

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

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

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


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

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

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

  • В случае, если ваши команды требуют взаимодействия с пользователем , такого как ответ на интерактивное приглашение, вы можете явно запросить создание pty (pseudo-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 (псевдотерминал), за исключением случаев, когда его стандартный ввод не подключен к (реальному) терминалу.

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

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

mklement0
источник
1
Это работало отлично, способ -tt заставлял терминал отображать каждую команду, отправленную через ssh.
Mark E
1
«Удвоение опции -t для принудительного создания pty: ssh -t -t ... или ssh -tt ... показывает, что вы действительно, действительно это имеете в виду». - ха, ха - спасибо, это смешно, но тоже серьезно - я не могу разобраться с двойным т, все, что я знаю, это то, что если я вставлю один т, это не сработает
DavidC
22

Я не знаю, откуда происходит зависание, но перенаправление (или передача) команд в интерактивный 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?

Хмахолм оставил Монику
источник
1
Опция -t была попыткой исправить проблему с терминалом Psuedo (она не работала). Я попробовал ваше решение, оно избавилось от штуки в терминале псевдо, но теперь оно просто зависает ...
Матфея
4
@Henning: Не могли бы вы уточнить или предоставить ссылку, касающуюся недостатков перенаправления или передачи в интерактивный ssh?
Исаак Кляйнман
немного поздно, но: здесь вам не нужен псевдотерминал, поэтому используйте опцию -T.
Флориан Кастеллан
6

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

ДОПОЛНИТЕЛЬНО :

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

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

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

ssh -t user@machine "$(<run-command.sh)" | tee -a logfile.log
Вадих М.
источник
0

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

Решение

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

ssh -t foobar @ localhost yourscript.pl

mxftw
источник
2
ОП -tтоже использует , но в их конкретном сценарии этого недостаточно, что побудило начать вопрос.
mklement0