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

Eu estou tentando escrever um script de shell que cria alguns diretórios em um servidor remoto e, em seguida, usa 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 eu executo, recebo esta mensagem:

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

E o roteiro apenas fica pendurado para sempre.

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

Tente ssh -t -t (ou ssh -tt para abreviar) para forçar a alocação pseudo-tty mesmo se stdin não for um terminal.

Veja também: Terminando a session SSH executada pelo script bash

De 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, eg when implementing menu services. Multiple -t options force tty allocation, even if ssh has no local tty. 

Também com a opção -T do manual

Desativar alocação pseudo-tty

Resposta do Perito , você não está fornecendo um comando remoto para o ssh , dado como o shell analisa a linha de comando. Para resolver esse problema, altere a syntax de sua chamada de comando ssh para que o comando remoto seja composto de uma cadeia de várias linhas sintaticamente correta.

Há uma variedade de syntaxs que podem ser usadas. Por exemplo, como os comandos podem ser canalizados para o bash e sh , e provavelmente para outros shells também, a solução mais simples é apenas combinar a invocação do shell ssh 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 

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

Se você é um fã de canos, você pode rewrite o acima da seguinte forma:

 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 advertência sobre /bin/bash se aplica ao acima.

Outra abordagem válida é passar o comando remoto de várias linhas como uma única string, usando várias camadas de interpolação de variável bash 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 sendo o comando ssh , seguido por um argumento user@server para ser passado para o comando ssh

  2. " inicia uma string interpolada, que quando completada, includeá um argumento a ser passado para o comando ssh , que neste caso será interpretado por ssh como sendo o comando remoto para executar como user@server

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

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

  5. << começa uma bash heredoc

  6. 'EOT' especifica que o nome do heredoc é EOT. O EOT em torno de aspas simples especifica que o heredoc deve ser analisado como um nowdoc , que é uma forma especial de heredoc em que o conteúdo não é interpolado por bash, mas sim transmitido em formato literal

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

  8. EOT encerra o nowdoc, resultando em um arquivo temporário nowdoc sendo criado e passado de volta para o comando calling cat . cat retorna o nowdoc e passa a saída de volta para a string interpolada de captura

  9. ) conclui o comando a ser executado

  10. " conclui a string interpolada de captura. O conteúdo da string interpolada será passado de volta para ssh como um único argumento de linha de comando, que o ssh interpretará como o comando remoto para executar como user@server

Se você precisar evitar o uso de ferramentas externas, como o cat , e não se importar em ter duas instruções em vez de uma, use a read integrada 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}" 

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

Problema : Eu tinha instalado 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 não tinha instalado o programa e os utilitários do cliente openssh. Por causa disso, 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.

A mensagem de aviso Pseudo-terminal will not be allocated because stdin is not a terminal. é devido ao fato de que nenhum comando é especificado para ssh enquanto stdin é redirecionado de um documento here. Devido à falta de um comando especificado como um argumento, o ssh espera primeiro uma session de login interativa (que requereria a alocação de um pty no host remoto), mas então tem que perceber que seu stdin local não é tty / pty. Redirecionar o stdin do ssh de um documento aqui normalmente requer que um comando (como /bin/sh ) seja especificado como um argumento para o ssh – e, nesse caso, nenhum pty será alocado no host remoto por padrão.

Como não há comandos a serem executados via ssh que exijam a presença de um tty / pty (como vim ou top ), o parâmetro -t para ssh é supérfluo. Apenas use ssh -T user@server < ou ssh user@server /bin/bash < e o aviso irá desaparecer.

Se < não for de escape ou simples (ie <<\EOT ou <<'EOT' ) as variables ​​dentro do documento aqui serão expandidas pelo shell local antes de executar ssh ... O efeito é que as variables ​​dentro do documento aqui permanecerão vazias, porque elas são definidas apenas no shell remoto.

Portanto, se $REL_DIR deve ser acessível pelo shell local e definido no shell remoto, $REL_DIR deve ser definido fora do documento here antes do comando ssh ( versão 1 abaixo); ou, se <<\EOT ou <<'EOT' for usado, a saída do comando ssh pode ser designada para REL_DIR se a única saída do comando ssh para stdout for gerada pelo echo "$REL_DIR" dentro do escape / único -quoted aqui documento ( versão 2 abaixo).

Uma terceira opção seria armazenar o documento aqui em uma variável e, em seguida, passar essa variável como um 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 sucesso (veja: 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 <&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" 

Eu não sei de onde vem o jeito, mas redirect (ou canalizar) comandos para um ssh interativo é, em geral, uma receita para problemas. É mais robusto usar o estilo comando-para-executar-como-um-ú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 argumento de linha de comando multilinha -limitado).

A mensagem do pseudo-terminal é por causa do seu -t que pede ao ssh para tentar fazer com que o ambiente que ele roda na máquina remota pareça um terminal real para os programas que rodam lá. Seu cliente ssh está se recusando a fazer isso porque sua própria input padrão não é um terminal, portanto, não tem como passar as APIs de terminais especiais da máquina remota para o terminal real no terminal local.

O que você estava tentando conseguir com isso?

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

tl; dr:

  • NÃO passe os comandos para rodar usando um argumento de linha de comando :
    ssh jdoe@server '...'

    • '...' seqüências '...' caracteres 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 um argumento funciona como está e:

  • o problema com o pseudo-terminal nem sequer surgirá.
  • você não precisará de uma instrução de exit no final de seus comandos, porque a session será automaticamente encerrada depois que os comandos forem processados.

Resumindo: passar comandos via stdin é um mecanismo que está em desacordo com o design do ssh e causa problemas que precisam ser resolvidos.
Continue lendo, se você quiser saber mais.


Informação de fundo opcional:

O mecanismo do ssh para aceitar comandos para executar no servidor de destino é um argumento de linha de comando : o operando final (argumento não opcional) aceita uma cadeia contendo um ou mais comandos do shell.

  • Por padrão, esses comandos são executados de forma autônoma, em um shell não interativo , sem o uso de um (pseudo) terminal (a opção -T está implícita), e a session termina automaticamente quando o último comando termina o processamento.

  • No caso de seus comandos exigirem 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 permite interagir com a session remota, usando a opção -t ; por exemplo:

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

    • Observe que o prompt de read interativo só funciona corretamente com um arquivo, portanto, a opção -t é necessária.

    • Usar 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 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 desse argumento, o ssh cria um shell interativo - incluindo quando você envia comandos via stdin , que é onde o problema começa:

  • Para um shell interativo , o ssh normalmente 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 o stdin do ssh não está mais conectado a um terminal, então nenhum pty é criado, e o ssh avisa da seguinte forma :
      Pseudo-terminal will not be allocated because stdin is not a terminal.

    • Mesmo a opção -t , cujo propósito expresso é solicitar a criação de um arquivo, não é suficiente neste caso : você receberá o mesmo aviso.

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

      • Talvez a justificativa para exigir esse passo muito deliberado é que as coisas podem não funcionar como esperado . Por exemplo, no macOS 10.12, o equivalente aparente do comando acima, fornecendo os comandos via stdin e usando -tt , não funciona corretamente; a session fica paralisada depois de responder ao prompt de read :
        ssh -tt jdoe@server <<<'read -p "Enter something: "; echo "Entered: [$REPLY]"'


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

Em um aperto, use -T e forneça os comandos via stdin , com um comando de exit à direita, mas observe que, se você também precisa de resources interativos, o uso de -tt no lugar de -T pode não funcionar.

Depois de ler muitas dessas respostas, pensei em compartilhar minha solução resultante. Tudo o que eu adicionei é /bin/bash antes do heredoc e 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 quiser um prompt interativo remoto, por exemplo, se o script que estiver executando remotamente solicitar uma senha ou outras informações, porque as soluções anteriores não permitirão que você digite nos prompts.

 ssh -t user@machine "$( 

E se você também quiser registrar toda a session em um arquivo logfile.log :

 ssh -t user@machine "$( 

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

Solução

  1. Mx customize-variable (e depois pressione Enter)
  2. tramp-default-method (e, em seguida, 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 o Cxf / user @ host: e, em seguida, insiro a senha. A conexão agora é feita corretamente no Emacs no Windows para o meu servidor remoto.

ssh -t foobar @ localhost yourscript.pl