O pseudo-terminal não será alocado porque stdin não é um terminal

345

Estou tentando escrever um script de shell que cria alguns diretórios em um servidor remoto e, em seguida, usa o scp para copiar arquivos da minha máquina local para o controle remoto. Aqui está o que eu tenho até agora:

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

Sempre que o executo, recebo esta mensagem:

Pseudo-terminal will not be allocated because stdin is not a terminal.

E o script trava para sempre.

Minha chave pública é confiável no servidor e posso executar todos os comandos fora do script. Alguma ideia?

Mateus
fonte
4
Você pode simplesmente especificar o terminal para usar comossh user@server /bin/bash <<EOT…
Buzut
3
@ Buzut: Você provavelmente quer dizer shell , mas, sim, especificar /bin/bashexplicitamente é uma maneira de evitar o problema.
usar o seguinte comando
11
@ mklement0 de fato, foi o que eu quis dizer. Thx para corrigir isso;)
Buzut

Respostas:

512

Tente ssh -t -t(ou ssh -ttabrevie) forçar a alocação de pseudo-tty, mesmo que stdin não seja um terminal.

Consulte também: Terminando a sessão SSH executada pelo script bash

Na página de manual do ssh:

-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
fonte
21
Estou tendo um problema semelhante em um script que é executado aqui. Eu adicionei o -t -t, mas agora estou recebendo um novo erro. "tcgetattr: ioctl inadequado para dispositivo"
MasterZ 18/12/12
5
Por que ssh -t -te não ssh -tt? Existe uma diferença que eu não estou ciente?
24414 Jack
3
@MasterZ A mesma coisa aqui. Seria bom para obter uma resposta a estaInappropriate IOCtl for device
krb686
11
@Jack -tte -t -tsão equivalentes; especificar args separadamente ou agrupados torna-se uma questão de estilo / preferência pessoal e, quando se trata disso, há argumentos válidos para fazê-lo de qualquer maneira. mas, na verdade, é apenas preferência pessoal.
JDS #
5
O que significa quando diz "mesmo que ssh não tenha tty local"?
precisa saber é o seguinte
192

Também com opção -Tde manual

Desativar alocação de pseudo-tty

Emil Bojda
fonte
11
Esta resposta precisa de mais pontos - sua a resposta certa, e não muito tempo como a resposta por zanco, sua absurda de que "oh alocar um TTY qualquer maneira com -t -t" recebe 107 pontos, quando você pode simplesmente ignorá-lo completamente
nhed
2
Apenas votado; ele tinha trabalhado melhor para mim do -t -t
Vincent Hiribarren
15
'-t -t' obras, porém com '-T' i get 'sudo: desculpe, você deve ter um tty para executar sudo'
Ivan Balashov
3
@ nhed: a -ttopção parece melhor para aqueles que realmente querem um TTY e estão recebendo a mensagem de erro do OP. Essa -Tresposta é melhor se você não precisar do TTY.
precisa saber é o seguinte
3
@ nhed - com certeza - mas tenho certeza de que outros como eu chegaram aqui no Google, pesquisando o erro na mensagem de erro. Não parece irracional deixar claro como outros googlers devem escolher entre duas respostas concorrentes. ou talvez não. Eu não sei
ErichBSchulz
91

De acordo com a resposta do zanco , você não está fornecendo um comando remoto ssh, considerando como o shell analisa a linha de comando. Para resolver esse problema, altere a sintaxe da sua sshchamada de comando para que o comando remoto seja composto por uma cadeia de linhas multilinhas sintaticamente correta.

Há uma variedade de sintaxes que podem ser usadas. Por exemplo, como os comandos podem ser canalizados bashe sh, e provavelmente outros shells, a solução mais simples é combinar a sshchamada de shell com os heredocs:

ssh user@server /bin/bash <<'EOT'
echo "These commands will be run on: $( uname -a )"
echo "They are executed by: $( whoami )"
EOT

Observe que a execução acima sem /bin/bash resultará no aviso Pseudo-terminal will not be allocated because stdin is not a terminal. Observe também que EOTestá entre aspas simples, de modo que bashreconhece o heredoc como um nowoc , desativando a interpolação de variável local para que o texto do comando seja passado como está ssh.

Se você é um fã de pipes, pode reescrever o que foi dito acima, da seguinte maneira:

cat <<'EOT' | ssh user@server /bin/bash
echo "These commands will be run on: $( uname -a )"
echo "They are executed by: $( whoami )"
EOT

A mesma ressalva /bin/bashse aplica ao acima.

Outra abordagem válida é passar o comando remoto com várias linhas como uma única sequência, usando várias camadas de bashinterpolação de variáveis ​​da seguinte maneira:

ssh user@server "$( cat <<'EOT'
echo "These commands will be run on: $( uname -a )"
echo "They are executed by: $( whoami )"
EOT
)"

A solução acima corrige esse problema da seguinte maneira:

  1. ssh user@serveré analisado pelo bash e é interpretado como o sshcomando, seguido por um argumento user@servera ser passado ao sshcomando

  2. "inicia uma cadeia de caracteres interpolada que, quando concluída, compreenderá um argumento a ser passado para o sshcomando, que neste caso será interpretado sshcomo o comando remoto a ser executado comouser@server

  3. $( inicia um comando a ser executado, com a saída sendo capturada pela sequência interpolada circundante

  4. caté um comando para gerar o conteúdo de qualquer arquivo a seguir. A saída de catserá passada de volta para a sequência interpolada de captura

  5. <<começa um heredoc da festança

  6. 'EOT'especifica que o nome do heredoc é EOT. As aspas simples ao 'redor do EOT especificam que o heredoc deve ser analisado como um nowdoc , que é uma forma especial de heredoc na qual o conteúdo não é interpolado pelo bash, mas transmitido no formato literal

  7. Qualquer conteúdo encontrado entre <<'EOT'e <newline>EOT<newline>será anexado à saída nowdoc

  8. EOTfinaliza o nowdoc, resultando em um arquivo temporário do nowdoc sendo criado e transmitido de volta ao catcomando de chamada . catgera o nowdoc e passa a saída de volta para a sequência interpolada de captura

  9. ) conclui o comando a ser executado

  10. "conclui a captura da cadeia interpolada. O conteúdo da cadeia de caracteres interpolada será passado de volta para sshum único argumento de linha de comando, que sshinterpretará como o comando remoto para executar comouser@server

Se você precisar evitar o uso de ferramentas externas como cat, e não se importar de ter duas instruções em vez de uma, use o readbuilt-in com um heredoc para gerar o comando 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}"
Dejay Clayton
fonte
+1 um ano e meio depois! :) explicação realmente clara. bem feito
yaroslavTir
11
Para mim (e eu não sou de forma alguma uma pessoa "DevOps"), foi isso que funcionou (estou usando Jenkins). Também tentei as sugestões "-t -t" e "-T", mas me deparei com problemas de fluxo aberto e problemas de não execução.
Ryan Crews
2 anos depois, realmente útil
jack-nie
+1 Explicação impressionante. Mas se você transformar o último trecho de código (IFS = '' *) em uma função para executar comandos remotos, como você passaria $ 1 para o IFS = '' *? Parece docente reconhecer o conteúdo de US $ 1.
Cristian Matthias Ambæk
11
@ mklement0 Claro, você pode escapar de seqüências de caracteres conforme necessário, mas quem quer o incômodo de modificar as instruções de script que você pode apenas copiar e colar de outro script de shell ou o stackoverflow? Além disso, a elegância da catsolução é que ela é realizada em uma única instrução (embora composta) e não resulta em variáveis ​​temporárias poluindo o ambiente do shell.
DeJay Clayton
63

Estou adicionando esta resposta porque resolveu um problema relacionado que eu estava tendo com a mesma mensagem de erro.

Problema : Instalei o cygwin no Windows e estava recebendo este erro:Pseudo-terminal will not be allocated because stdin is not a terminal

Resolução : Acontece que eu tinha não instalado o programa e os utilitários do openssh. Por esse motivo, o cygwin estava usando a implementação do ssh no Windows, não a versão do cygwin. A solução foi instalar o pacote openssh cygwin.

Andrew Prock
fonte
10
Para mim, verificou-se que era outra implementação ssh no PATH: $ which ssh/cygdrive/c/Program Files (x86)/Git/bin/ssh
#
e instalá- opensshlo pode ser o caminho a seguir para o Windows: superuser.com/a/301026/260710
Andreas Dietrich
Esta solução funciona para mim. Depois de instalar o openssh, o problema desapareceu.
Jdhao 23/1018
34

A mensagem de aviso Pseudo-terminal will not be allocated because stdin is not a terminal.deve-se ao fato de nenhum comando ser especificado sshenquanto o stdin é redirecionado de um documento aqui. Devido à falta de um comando especificado, o argumento sshprimeiro espera uma sessão de logon interativa (o que exigiria a alocação de um pty no host remoto), mas é necessário perceber que seu stdin local não é tty / pty. O redirecionamento de sshstdin de um documento aqui normalmente exige que um comando (como /bin/sh) seja especificado como argumento para ssh- e, nesse caso, nenhum pty será alocado no host remoto por padrão.

Como não há comandos a serem executados via sshque exijam a presença de um tty / pty (como vimou top) o -tcomutador para sshé supérfluo. Apenas usessh -T user@server <<EOT ... ou ssh user@server /bin/bash <<EOT ...e o aviso desaparecerá.

Se <<EOFnão for escapado ou as variáveis ​​de aspas simples ( <<\EOTou seja <<'EOT') dentro do documento aqui serão expandidas pelo shell local antes de serem executadas ssh .... O efeito é que as variáveis ​​contidas no documento aqui permanecerão vazias porque são definidas apenas no shell remoto.

Portanto, se $REL_DIRdeve ser acessível pelo shell local e definido no shell remoto, $REL_DIRdeve ser definido fora do documento aqui antes do sshcomando ( versão 1 abaixo); ou, se <<\EOTou <<'EOT'é usado, a saída do sshcomando pode ser atribuído a REL_DIRse a única saída do sshcomando para stdout é genererated por echo "$REL_DIR"dentro do / documento único citado aqui (escapou versão 2 abaixo).

Uma terceira opção seria armazenar o documento aqui em uma variável e depois passá-la como argumento de comando para ssh -t user@server "$heredoc"( versão 3 abaixo).

E, por último, mas não menos importante, não seria uma má idéia verificar se os diretórios no host remoto foram criados com êxito (consulte: verifique se o arquivo existe no host remoto com 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
fonte
5
Obrigado pela explicação muito detalhada sobre o que esse erro significa e por que ele aparece ao usar um heredoc, isso me ajudou a realmente entendê-lo, em vez de apenas contorná-lo.
Dan
29

Todas as informações relevantes estão nas respostas existentes, mas deixe-me tentar um resumo pragmático :

tl; dr:

  • Passe os comandos para execução usando um argumento de linha de comando :
    ssh jdoe@server '...'

    • '...' strings podem abranger várias linhas, para que você possa manter seu código legível mesmo sem o uso de um documento aqui:
      ssh jdoe@server ' ... '
  • NÃO passe os comandos via stdin , como é o caso quando você usa um documento aqui :
    ssh jdoe@server <<'EOF' # Do NOT do this ... EOF

Passar os comandos como argumento funciona como está e:

  • o problema com o pseudo-terminal nem sequer surgirá.
  • você não precisará de uma exitdeclaração no final de seus comandos, porque a sessão será encerrada automaticamente após o processamento dos comandos.

Resumindo: passar comandos via stdin é um mecanismo que está em desacordo com ssho design e causa problemas que devem ser contornados.
Continue lendo, se você quiser saber mais.


Informações complementares opcionais:

sshO mecanismo de aceitação de comandos para execução no servidor de destino é um argumento da linha de comandos : o operando final (argumento não opcional) aceita uma sequência que contém um ou mais comandos shell.

  • Por padrão, esses comandos são executados sem supervisão, em um shell não interativo , sem o uso de um terminal (pseudo) (opção -Timplícita), e a sessão termina automaticamente quando o último comando termina o processamento.

  • Caso seus comandos exijam interação do usuário , como responder a um prompt interativo, você pode solicitar explicitamente a criação de um pty (pseudo-tty) , um pseudo terminal, que permita interagir com a sessão remota, usando o-t opção; por exemplo:

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

    • Observe que o interativo read prompt funciona apenas corretamente com um pty, portanto, a -topção é necessária.

    • O uso de um pty tem um efeito colateral notável: stdout e stderr são combinados e ambos relatados via stdout ; em outras palavras: você perde a distinção entre saída regular e saída de erro; por exemplo:

      • 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

Na ausência deste argumento, sshcria um shell interativo - inclusive quando você envia comandos via stdin , que é onde o problema começa:

  • Para um shell interativo , sshnormalmente aloca um pty (pseudo-terminal) por padrão, exceto se seu stdin não estiver conectado a um terminal (real).

    • Enviar comandos via stdin significa que ssho stdin não está mais conectado a um terminal, portanto, nenhum pty é criado e ssh avisa você de acordo :
      Pseudo-terminal will not be allocated because stdin is not a terminal.

    • Mesmo a -topção, cujo objetivo expresso é solicitar a criação de um pty, não é suficiente neste caso : você receberá o mesmo aviso.

      • Um pouco curiosamente, você deve dobrar a -topção de forçar a criação de um pty: ssh -t -t ...ou ssh -tt ...mostra que você realmente, realmente quer dizer isso .

      • Talvez a justificativa para exigir essa etapa muito deliberada seja que as coisas podem não funcionar como o esperado . Por exemplo, no macOS 10.12, o aparente equivalente do comando acima, fornecendo os comandos via stdin e usando -tt, não funciona corretamente; a sessão fica presa após responder ao readprompt:
        ssh -tt jdoe@server <<<'read -p "Enter something: "; echo "Entered: [$REPLY]"'


No caso improvável de que os comandos que você deseja passar como argumento tornem a linha de comando muito longa para o seu sistema (se o comprimento se aproximar getconf ARG_MAX- consulte este artigo ), considere primeiro copiar o código para o sistema remoto na forma de um script ( usando, por exemplo, scp) e envie um comando para executar esse script.

Em uma pitada, use -Te forneça os comandos via stdin , com um exitcomando à direita , mas observe que se você também precisar de recursos interativos, o uso -ttno lugar de -Tpode não funcionar.

mklement0
fonte
11
Isso funcionou perfeitamente, a maneira -tt estava fazendo o terminal exibir todos os comandos enviados pelo ssh.
Mark E
11
"duplica a opção -t para forçar a criação de um pty: ssh -t -t ... ou ssh -tt ... mostra que você realmente, realmente quer dizer isso." - ha, ha - graças engraçado, mas grave também - eu não posso colocar minha cabeça em torno da dupla t Tudo que eu sei é se eu colocar em um único t ele não wrok
DavidC
22

Não sei de onde vem o travamento, mas redirecionar (ou canalizar) comandos para um ssh interativo geralmente é uma receita para problemas. É mais robusto usar o estilo de comando para executar como último argumento e passar o script na linha de comando 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'

(Tudo em um gigante ' único argumento de linha de comando multilinha e delimitado).

A mensagem do pseudo-terminal é por causa da sua, -tque pede ao ssh para tentar fazer com que o ambiente em que é executado na máquina remota pareça um terminal real para os programas que são executados lá. Seu cliente ssh está se recusando a fazer isso porque sua própria entrada padrão não é um terminal, portanto, não há como passar as APIs especiais do terminal da máquina remota para o seu terminal real na extremidade local.

O que você estava tentando alcançar de -tqualquer maneira?

hmakholm deixou sobre Monica
fonte
11
A opção -t foi uma tentativa de corrigir o problema do terminal Psuedo (não funcionou). Eu tentei sua solução, ele se livrou da coisa terminal de pseudo mas agora ele só trava ...
Matthew
4
@ Henning: Você pode elaborar ou fornecer um link sobre as desvantagens de redirecionar ou canalizar para um ssh interativo?
Isaac Kleinman
um pouco tarde, mas: aqui você não precisa de um pseudo terminal, então use a opção -T.
Florian Castellane
6

Depois de ler muitas dessas respostas, pensei em compartilhar minha solução resultante. Tudo o que eu adicionei é /bin/bashantes do heredoc e ele não dá mais o erro.

Usa isto:

ssh user@machine /bin/bash <<'ENDSSH'
   hostname
ENDSSH

Em vez disso (dá erro):

ssh user@machine <<'ENDSSH'
   hostname
ENDSSH

Ou use isto:

ssh user@machine /bin/bash < run-command.sh

Em vez disso (dá erro):

ssh user@machine < run-command.sh

EXTRA :

Se você ainda deseja um prompt interativo remoto, por exemplo, se o script que você está executando remotamente solicitar uma senha ou outras informações, porque as soluções anteriores não permitirão que você digite os prompts.

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

E se você também deseja registrar a sessão inteira em um arquivo logfile.log:

ssh -t user@machine "$(<run-command.sh)" | tee -a logfile.log
Wadih M.
fonte
0

Eu estava tendo o mesmo erro no Windows usando o emacs 24.5.1 para conectar-se a alguns servidores da empresa por meio de / ssh: user @ host. O que resolveu meu problema foi definir a variável "tramp-default-method" como "plink" e sempre que eu me conecto a um servidor, omito o protocolo ssh. Você precisa ter o plink.exe do PuTTY instalado para que isso funcione.

Solução

  1. Mx customize-variable (e pressione Enter)
  2. tramp-default-method (e depois pressione Enter novamente)
  3. No campo de texto, coloque plink e, em seguida, aplique e salve o buffer
  4. Sempre que tento acessar um servidor remoto, agora uso Cxf / user @ host: e insiro a senha. Agora a conexão foi feita corretamente no Emacs no Windows para o meu servidor remoto.
Miguel Rentes
fonte
-3

ssh -t foobar @ localhost seu script.pl

mxftw
fonte
2
O OP -ttambém usa , mas em seu cenário específico isso não é suficiente, o que levou a questão a começar.
usar o seguinte comando