Псевдотермінал не буде виділено, оскільки 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 справді, це я мав на увазі. Thx для виправлення цього;)
Buzut

Відповіді:


512

Спробуйте 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.

21
У мене є аналогічна проблема в сценарії, який виконується тут. Я додав -t -t, але тепер я отримую нову помилку. "tcgetattr: Невідповідний ioctl для пристрою"
MasterZ

5
Чому ssh -t -tі ні ssh -tt? Чи є різниця, про яку я не знаю?
Джек

3
@MasterZ Тут те саме. Було б непогано отримати відповідь на цеInappropriate IOCtl for device
krb686

11
@Jack -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 і отримує повідомлення про помилку ОП. Ця -Tвідповідь краще, якщо вам не потрібно TTY.
ErichBSchulz

3
@nhed - впевнений - але я впевнений, що такі, як я, потрапили сюди від Googling про помилку в повідомленні про помилку. Здається, нерозумно пояснити, як інші googlers повинні обирати між двома конкуруючими відповідями. а може й ні. я не знаю
ErichBSchulz

91

Відповідно до zanco , ви не надаєте віддалену команду ssh, враховуючи, як оболонка аналізує командний рядок. Щоб вирішити цю проблему, змініть синтаксис sshвиклику вашої команди так, щоб віддалена команда складалася з синтаксично правильного багаторядкового рядка.

Існують різноманітні синтаксиси, які можна використовувати. Наприклад, оскільки команди можуть бути вкладені в bashі sh, можливо, і в інші оболонки, найпростішим рішенням є просто поєднання sshвиклику оболонки з гередоками:

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

  6. 'EOT'вказує, що ім'я гередок - EOT. Єдині цитати 'навколо EOT вказують, що гередок повинен бути проаналізований як теперішній документ , що є особливою формою гередока, в якій вміст не інтерполюється башем, а передається у прямому форматі

  7. Будь-який вміст, який зустрічається між собою <<'EOT'та <newline>EOT<newline>буде доданий до виводу nowdoc

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

  9. ) завершує команду, яку потрібно виконати

  10. "завершує захоплення інтерпольованого рядка. Вміст інтерпольованого рядка буде передано назад sshяк єдиний аргумент командного рядка, який sshінтерпретуватиме як віддалену команду для виконання якuser@server

Якщо вам потрібно уникати використання зовнішніх інструментів, таких як cat, і не заперечуйте, щоб мати два оператори замість одного, використовуйте readвбудований гередок для створення команди 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") це те, що працювало (я використовую Дженкінса). Я також спробував пропозиції "-t -t" і "-T", але наткнувся на проблеми з відкритим потоком та проблеми з невиконанням.
Райан Екіпаж

Через 2 роки, справді корисно
jack-nie

+1 Дивовижне пояснення. Але якщо ви зробите останній фрагмент коду (IFS = '' *) функцією для виконання віддалених команд, як би ви перевели $ 1 в IFS = '' *? Здається, доцент взагалі розпізнає вміст у розмірі 1 долара.
Крістіан Маттіас Амбек

1
@ mklement0 Звичайно, ви можете уникнути рядків у міру необхідності, але хто хоче клопоту з необхідністю змінювати заяви сценарію, які ви можете просто копіювати та вставляти з іншого сценарію оболонки або stackoverflow для цього питання? Крім того, елегантність catрішення полягає в тому, що воно виконується в одному (хоча і складеному) твердженні і не призводить до тимчасових змінних, що забруднюють середовище оболонки.
Dejay Clayton

63

Я додаю цю відповідь, тому що вона вирішила пов'язану проблему, з якою у мене виникло те саме повідомлення про помилку.

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

Розв’язання . Виявляється, я не встановив клієнтську програму OpenBS і утиліти. Через це cygwin використовував реалізацію ssh, а не версію cygwin. Рішенням було встановити відкритий пакет cygwin.


10
Для мене виявилося, що це ще одна реалізація ssh в PATH:$ which ssh/cygdrive/c/Program Files (x86)/Git/bin/ssh
FelixJongleur42

і встановити opensshце може бути шлях для Windows: superuser.com/a/301026/260710
Andreas Dietrich

Це рішення працює для мене. Після встановлення opensh проблему немає.
jdhao

34

Попереджувальне повідомлення Pseudo-terminal will not be allocated because stdin is not a terminal.пояснюється тим, що жодна команда не вказана, sshпоки stdin перенаправляється з документа тут. Через відсутність зазначеної команди в якості аргументу 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') змінні всередині документу тут буде розширено локальною оболонкою перед її виконанням 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"

5
Дякую за дуже детальне пояснення того, що означає ця помилка та чому вона з’являється при використанні гередока, це допомогло мені насправді зрозуміти її, а не просто обійти її.
Dan

29

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

tl; dr:

  • НЕ передайте команди для запуску за допомогою аргументу командного рядка :
    ssh jdoe@server '...'

    • '...' рядки можуть охоплювати кілька рядків, тому ви можете зберігати свій код читабельним навіть без використання документа тут:
      ssh jdoe@server ' ... '
  • НЕ передайте команди через stdin , як це відбувається у випадку використання тут документа :
    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 (псевдотермінал), за винятком випадків, коли його stdin не підключений до (реального) терміналу.

    • Відправка команд через не означає , що стандартне введення 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може не працювати.


1
Це спрацювало ідеально, -tt спосіб робив показ терміналу кожну команду, що надсилається через ssh.
Марк Е

1
"подвійний варіант -t, щоб змусити створити pty: ssh -t -t ... або ssh -tt ..., показує, що ти насправді маєш на увазі це." - ха-ха - дякую, що це смішно, але теж серйозно - я не можу
змайструвати

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 (вона не спрацювала). Я спробував ваше рішення, він позбувся речі терміналу psuedo, але тепер він просто зависає ...
Меттью

4
@Henning: Чи можете ви, будь ласка, розробити або надати посилання щодо недоліків перенаправлення чи перетворення в інтерактивний ssh?
Ісаак Кляйнман

трохи пізно, але: тут вам не потрібен псевдотермінал, тому використовуйте -Tнатомість варіант .
Флоріан Кастеллан

6

Прочитавши багато цих відповідей, я подумав, що поділюся отриманим рішенням. Все, що я додав, є /bin/bashперед гередоком, і це більше не дає помилки.

Використовуй це:

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. Для цього вам потрібно встановити plink.exe PuTTY.

Рішення

  1. Mx налаштування змінної (а потім натисніть Enter)
  2. метод tramp-default (і знову натисніть Enter)
  3. У текстовому полі покладіть планк, а потім застосуйте та збережіть буфер
  4. Кожен раз, коли я намагаюся отримати доступ до віддаленого сервера, я тепер використовую Cxf / user @ host: і потім вводите пароль. Підключення тепер правильно виконано в Emacs в Windows на мій віддалений сервер.

-3

ssh -t foobar @ localhost yourscript.pl


2
ОП також використовує -t, але в їх конкретному сценарії цього недостатньо, що спонукало почати питання.
mklement0
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.