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

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


попробовать 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.

тоже вариант -T с руководство

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


Per 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 выводит 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}"

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


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

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

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

Итак, если $REL_DIR должен быть как доступен локальной оболочкой, так и определен в удаленной оболочке,$REL_DIR должен быть определен вне документа перед (Вариант 1 ниже); или, если <<\EOT или <<'EOT' используется, выход может быть присвоено REL_DIR если единственный выход в stdout, genererated по 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"

Я не знаю, откуда происходит зависание, но перенаправление (или конвейерные) команды в интерактивный ssh, как правило, рецепт для проблем. Более надежным является использование стиля command-to-run-as-a-last-argument и передача скрипта в командной строке 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 в любом случае?


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

tl; dr:

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

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

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

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

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


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

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

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

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

    • ssh -t jdoe@server 'read -p "Enter something: "; echo "Entered: [$REPLY]"'

    • обратите внимание, что интерактивный read запрос работает только правильно с pty, так что -t опция необходима.

    • использование pty имеет заметный побочный эффект: stdout и stderr являются в сочетании и сообщили через стандартный вывод; другими словами: вы теряете различие между регулярным и выводом ошибок; например:

      • 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 создает интерактивные shell - в том числе при отправке команд через stdin, где и начинается беда:

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

    • отправка команды через stdin означает, что sshstdin больше не подключен к терминалу, поэтому нет pty создан, и ssh предупреждает вы соответственно:
      Pseudo-terminal will not be allocated because stdin is not a terminal.

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

      • немного странно, то вы должны двойной the -t опции форсировать создание Пти: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 может не работать.


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

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

решение

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

ssh - t foobar@localhost yourscript.pl