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:
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
"
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
$(
inicia um comando para ser executado, com a saída sendo capturada pela cadeia interpolada circundante
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
<<
começa uma bash heredoc
'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
Qualquer conteúdo encontrado entre <<'EOT'
e
será anexado à saída nowdoc
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
)
conclui o comando a ser executado
"
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 <
ssh user@server /bin/bash <
Se <
<<\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:
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.
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
ssh -t foobar @ localhost yourscript.pl