Le pseudo-terminal ne sera pas alloué car stdin n'est pas un terminal

345

J'essaie d'écrire un script shell qui crée des répertoires sur un serveur distant et utilise ensuite scp pour copier des fichiers de ma machine locale sur la télécommande. Voici ce que j'ai jusqu'à présent:

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

Chaque fois que je l'exécute, je reçois ce message:

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

Et le script se bloque pour toujours.

Ma clé publique est approuvée sur le serveur et je peux très bien exécuter toutes les commandes en dehors du script. Des idées?

Matthieu
la source
4
Vous pouvez simplement spécifier le terminal à utiliser commessh user@server /bin/bash <<EOT…
Buzut
3
@Buzut: Vous voulez probablement dire shell , mais, oui, spécifier /bin/bashexplicitement est un moyen d'éviter le problème.
mklement0
1
@ mklement0 en effet, c'est ce que je voulais dire. Merci d'avoir corrigé cela;)
Buzut

Réponses:

513

Essayez ssh -t -t(ou ssh -ttpour faire court) de forcer l'allocation de pseudo-tty même si stdin n'est pas un terminal.

Voir aussi: Terminer la session SSH exécutée par le script bash

Depuis la page de manuel de 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
la source
21
J'ai un problème similaire dans un script exécuté ici. J'ai ajouté le -t -t mais maintenant j'obtiens une nouvelle erreur. "tcgetattr: ioctl inapproprié pour le périphérique"
MasterZ
5
Pourquoi ssh -t -tet non ssh -tt? Y a-t-il une différence que je ne connais pas?
Jack
3
@MasterZ Même chose ici. Ce serait bien d'avoir une réponse à celaInappropriate IOCtl for device
krb686
11
@Jack -ttet -t -tsont équivalents; la spécification d'arguments séparément ou de smooshed ensemble devient une question de style / préférence personnelle, et quand il s'agit, il y a des arguments valables pour le faire de toute façon. mais vraiment, c'est juste une préférence personnelle.
JDS
5
Qu'est-ce que cela signifie quand il dit "même si ssh n'a pas de terminal local"?
CMCDragonkai
192

Également avec option -Tdu manuel

Désactiver l'allocation de pseudo-tty

Emil Bojda
la source
11
Cette réponse a besoin de plus de points - c'est la bonne réponse, et pas trop longtemps comme la réponse de zanco, son absurde que "oh alloue quand même un ATS avec -t -t" obtient 107 points, quand vous pouvez simplement le sauter complètement
nhed
2
Voté juste; cela a mieux fonctionné pour moi que -t -t
Vincent Hiribarren
15
'-t -t' fonctionne, cependant avec '-T' je reçois 'sudo: désolé, vous devez avoir un tty pour exécuter sudo'
Ivan Balashov
3
@nhed: L' -ttoption apparaît mieux pour ceux d'entre nous qui veulent réellement un ATS et reçoivent le message d'erreur de l'OP. Cette -Tréponse est meilleure si vous n'avez pas besoin du téléscripteur.
ErichBSchulz
3
@nhed - bien sûr - mais je suis sûr que d'autres comme moi sont arrivés ici de googler l'erreur dans le message d'erreur. Il ne semble pas déraisonnable de préciser comment les autres googleurs devraient choisir entre 2 réponses concurrentes. ou peut être pas. je ne sais pas
ErichBSchulz
91

Selon la réponse de Zanco , vous ne fournissez pas de commande à distance ssh, étant donné la façon dont le shell analyse la ligne de commande. Pour résoudre ce problème, modifiez la syntaxe de votressh appel de commande afin que la commande distante soit composée d'une chaîne multi-lignes syntaxiquement correcte.

Il existe une variété de syntaxes qui peuvent être utilisées. Par exemple, puisque les commandes peuvent être canalisées dans bashet sh, et probablement aussi d'autres shells, la solution la plus simple est de simplement combiner l' sshinvocation du shell avec heredocs:

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

Notez que l'exécution de ce qui précède sans /bin/bash entraînera l'avertissement Pseudo-terminal will not be allocated because stdin is not a terminal. Notez également qu'il EOTest entouré de guillemets simples, ce qui bashreconnaît l'heredoc comme un nowdoc , désactivant l'interpolation de variable locale afin que le texte de la commande soit transmis tel quel àssh .

Si vous êtes un fan de tuyaux, vous pouvez réécrire ce qui suit comme suit:

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

La même mise en garde à propos de /bin/bash s'applique à ce qui précède.

Une autre approche valide consiste à passer la commande à distance multi-ligne en une seule chaîne, en utilisant plusieurs couches d' bashinterpolation variable comme suit:

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

La solution ci-dessus résout ce problème de la manière suivante:

  1. ssh user@serverest analysé par bash, et est interprété comme étant la sshcommande, suivi d'un argument user@serverà passer à la sshcommande

  2. "commence une chaîne interpolée qui, une fois terminée, comprendra un argument à passer à la sshcommande, qui dans ce cas sera interprété par sshcomme étant la commande distante à exécuter commeuser@server

  3. $( commence une commande à exécuter, la sortie étant capturée par la chaîne interpolée environnante

  4. catest une commande pour afficher le contenu du fichier suivant. La sortie de catsera retransmise dans la chaîne interpolée de capture

  5. <<commence un hérédoc bash

  6. 'EOT'spécifie que le nom de l'hérédoc est EOT. Les guillemets simples 'entourant EOT spécifient que l'heredoc doit être analysé en tant que nowdoc , qui est une forme spéciale d'heredoc dans laquelle le contenu n'est pas interpolé par bash, mais plutôt transmis au format littéral

  7. Tout contenu rencontré entre <<'EOT'et <newline>EOT<newline>sera ajouté à la sortie nowdoc

  8. EOTtermine nowdoc, ce qui entraîne la création et la retransmission d'un fichier temporaire nowdoc à la catcommande appelante . catsort le nowdoc et retransmet la sortie à la chaîne interpolée de capture

  9. ) conclut la commande à exécuter

  10. "conclut la capture de la chaîne interpolée. Le contenu de la chaîne interpolée sera retransmis en sshtant qu'argument de ligne de commande unique, qui sshsera interprété comme la commande distante à exécuter commeuser@server

Si vous devez éviter d'utiliser des outils externes comme cat, et cela ne vous dérange pas d'avoir deux instructions au lieu d'une, utilisez le readintégré avec un hérédoc pour générer la commande 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
la source
+1 un an et demi plus tard! :) explication vraiment claire. bravo
yaroslavTir
1
Pour moi (et je ne suis en aucun cas une personne "DevOps"), c'est ce qui a fonctionné (j'utilise Jenkins). J'ai également essayé les suggestions "-t -t" et "-T", mais j'ai rencontré des problèmes de flux ouvert et des problèmes de non-exécution.
Ryan Crews
2 ans plus tard, vraiment utile
jack-nie
+1 Explication impressionnante. Mais si vous faites du dernier extrait de code (IFS = '' *) une fonction pour exécuter des commandes à distance, comment passeriez-vous $ 1 dans IFS = '' *? Il semble que docent reconnaisse le contenu de 1 $.
Cristian Matthias Ambæk
1
@ mklement0 Bien sûr, vous pouvez échapper les chaînes selon vos besoins, mais qui veut avoir les tracas d'avoir à modifier les instructions de script que vous pourriez simplement copier et coller à partir d'un autre script shell, ou stackoverflow d'ailleurs? En outre, l'élégance de la catsolution est qu'elle est accomplie dans une seule instruction (bien que composée) et n'entraîne pas de variables temporaires polluant l'environnement shell.
Dejay Clayton
63

J'ajoute cette réponse car elle a résolu un problème connexe que j'avais avec le même message d'erreur.

Problème : j'avais installé cygwin sous Windows et j'obtenais cette erreur:Pseudo-terminal will not be allocated because stdin is not a terminal

Résolution : il s'avère que je n'avais pas installé le programme client et les utilitaires openssh. À cause de cela, cygwin utilisait l'implémentation Windows de ssh, pas la version cygwin. La solution consistait à installer le package openssh cygwin.

Andrew Prock
la source
10
Pour moi, il s'est avéré que c'était une autre implémentation ssh dans le PATH:$ which ssh/cygdrive/c/Program Files (x86)/Git/bin/ssh
FelixJongleur42
et pour installer opensshcela peut être la voie à suivre pour Windows: superuser.com/a/301026/260710
Andreas Dietrich
Cette solution fonctionne pour moi. Après avoir installé openssh, le problème a disparu.
jdhao
34

Le message d'avertissement Pseudo-terminal will not be allocated because stdin is not a terminal.est dû au fait qu'aucune commande n'est spécifiée pendant sshque stdin est redirigé à partir d'un document ici. En raison de l'absence d'une commande spécifiée, un argument sshattend d'abord une session de connexion interactive (qui nécessiterait l'allocation d'un pty sur l'hôte distant), mais doit ensuite se rendre compte que son stdin local n'est pas tty / pty. La redirection sshde stdin à partir d'un document ici nécessite normalement une commande (telle que /bin/sh) à spécifier comme argument ssh- et dans ce cas, aucun pty ne sera alloué sur l'hôte distant par défaut.

Puisqu'il n'y a pas de commandes à exécuter via sshqui nécessitent la présence d'un tty / pty (comme vimou top), le -tcommutateur vers sshest superflu. Utilisez simplement ssh -T user@server <<EOT ...ou ssh user@server /bin/bash <<EOT ...et l'avertissement disparaîtra.

If <<EOFn'est pas échappé ou les variables entre guillemets simples (ie <<\EOTou <<'EOT') à l'intérieur du document ici seront développées par le shell local avant son exécution ssh .... L'effet est que les variables à l'intérieur du document ici resteront vides car elles sont définies uniquement dans le shell distant.

Donc, si $REL_DIRdoit être à la fois accessible par le shell local et défini dans le shell distant, $REL_DIRdoit être défini en dehors du document here avant la sshcommande ( version 1 ci-dessous); ou, si <<\EOTou <<'EOT'est utilisé, la sortie de la sshcommande peut être affectée à REL_DIRsi la seule sortie de la sshcommande vers stdout est générée par l' echo "$REL_DIR"intérieur du document d'échappement / guillemets simples ici ( version 2 ci-dessous).

Une troisième option serait de stocker le document ici dans une variable, puis de passer cette variable comme argument de commande à ssh -t user@server "$heredoc"( version 3 ci-dessous).

Et, last but not least, ce ne serait pas une mauvaise idée de vérifier si les répertoires sur l'hôte distant ont été créés avec succès (voir: vérifier si le fichier existe sur l'hôte distant avec 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
la source
5
Merci pour l'explication très détaillée de ce que signifie cette erreur et pourquoi elle apparaît lors de l'utilisation d'un hérédoc, cela m'a aidé à le comprendre plutôt qu'à le contourner.
Dan
29

Toutes les informations pertinentes se trouvent dans les réponses existantes, mais permettez-moi de tenter un résumé pragmatique :

tl; dr:

  • PASSEZ les commandes à exécuter à l'aide d'un argument de ligne de commande :
    ssh jdoe@server '...'

    • '...' les chaînes peuvent s'étendre sur plusieurs lignes, vous pouvez donc garder votre code lisible même sans l'utilisation d'un document ici:
      ssh jdoe@server ' ... '
  • Ne passez PAS les commandes via stdin , comme c'est le cas lorsque vous utilisez un document ici :
    ssh jdoe@server <<'EOF' # Do NOT do this ... EOF

Passer les commandes comme argument fonctionne tel quel et:

  • le problème avec le pseudo-terminal ne se posera même pas.
  • vous n'aurez pas besoin d'une exitinstruction à la fin de vos commandes, car la session se fermera automatiquement après le traitement des commandes.

En bref: passer des commandes via stdin est un mécanisme qui est en contradiction avec sshla conception de et provoque des problèmes qui doivent ensuite être contournés.
Continuez à lire, si vous voulez en savoir plus.


Informations générales facultatives:

sshLe mécanisme d'acceptation des commandes à exécuter sur le serveur cible est un argument de ligne de commande : l'opérande final (argument sans option) accepte une chaîne contenant une ou plusieurs commandes shell.

  • Par défaut, ces commandes s'exécutent sans surveillance, dans un shell non interactif , sans l'utilisation d'un (pseudo) terminal (option -Timplicite), et la session se termine automatiquement lorsque la dernière commande termine le traitement.

  • Dans le cas où vos commandes nécessitent une interaction de l'utilisateur , comme répondre à une invite interactive, vous pouvez explicitement demander la création d'un pty (pseudo-tty) , un pseudo terminal, qui permet d'interagir avec la session distante, en utilisant l' -toption; par exemple:

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

    • Notez que l' readinvite interactive ne fonctionne correctement qu'avec un pty, donc l' -toption est nécessaire.

    • L'utilisation d'un pty a un effet secondaire notable: stdout et stderr sont combinés et tous deux signalés via stdout ; en d'autres termes: vous perdez la distinction entre sortie régulière et sortie d'erreur; par exemple:

      • 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

En l'absence de cet argument, sshcrée un shell interactif - y compris lorsque vous envoyez des commandes via stdin , où commence le problème:

  • Pour un shell interactif , sshalloue normalement un pty (pseudo-terminal) par défaut, sauf si son stdin n'est pas connecté à un (vrai) terminal.

    • L'envoi de commandes via stdin signifie que sshstdin n'est plus connecté à un terminal, donc aucun pty n'est créé, et vous ssh avertit en conséquence :
      Pseudo-terminal will not be allocated because stdin is not a terminal.

    • Même l' -toption, dont le but exprès est de demander la création d'un pty, ne suffit pas dans ce cas : vous obtiendrez le même avertissement.

      • Curieusement, vous devez ensuite doubler l' -toption pour forcer la création d'un pty: ssh -t -t ...ou ssh -tt ...montrer que vous le pensez vraiment, vraiment .

      • La raison pour laquelle cette étape très délibérée est peut-être justifiée est que les choses peuvent ne pas fonctionner comme prévu . Par exemple, sur macOS 10.12, l'équivalent apparent de la commande ci-dessus, fournissant les commandes via stdin et utilisant -tt, ne fonctionne pas correctement; la session se bloque après avoir répondu à l' readinvite:
        ssh -tt jdoe@server <<<'read -p "Enter something: "; echo "Entered: [$REPLY]"'


Dans le cas peu probable où les commandes que vous souhaitez passer en argument rendent la ligne de commande trop longue pour votre système (si sa longueur approche getconf ARG_MAX- voir cet article ), envisagez d'abord de copier le code sur le système distant sous la forme d'un script ( en utilisant, par exemple, scp), puis envoyez une commande pour exécuter ce script.

Dans un pincement, utilisez -Tet fournissez les commandes via stdin , avec une exitcommande de fin , mais notez que si vous avez également besoin de fonctionnalités interactives, l'utilisation -ttau lieu de -Tpeut ne pas fonctionner.

mklement0
la source
1
Cela fonctionnait parfaitement, la méthode -tt faisait afficher au terminal chaque commande envoyée via ssh.
Mark E
1
"Doublez l'option -t pour forcer la création d'un pty: ssh -t -t ... ou ssh -tt ... montre que vous le pensez vraiment, vraiment." - ha, ha - merci, c'est drôle mais sérieux aussi - je ne peux pas comprendre le double t tout ce que je sais, c'est que si je mets un seul t ça ne marche pas
DavidC
22

Je ne sais pas d'où vient le blocage, mais rediriger (ou canaliser) les commandes vers un ssh interactif est en général une recette pour les problèmes. Il est plus robuste d'utiliser le style de commande pour exécuter en tant que dernier argument et de passer le script sur la ligne de commande 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'

(Tout en un géant ' argument de ligne de commande multiligne délimité ).

Le message pseudo-terminal est dû à votre message -tqui demande à ssh d'essayer de faire en sorte que l'environnement qu'il exécute sur la machine distante ressemble à un terminal réel pour les programmes qui s'y exécutent. Votre client ssh refuse de le faire car sa propre entrée standard n'est pas un terminal, il n'a donc aucun moyen de transmettre les API de terminal spéciales de la machine distante à votre terminal réel à l'extrémité locale.

Qu'essayiez-vous de faire de -ttoute façon?

hmakholm a laissé Monica
la source
1
L'option -t était une tentative de résoudre le problème du terminal Psuedo (cela n'a pas fonctionné). J'ai essayé votre solution, elle s'est débarrassée du truc du terminal psuedo mais maintenant ça ne fait que se bloquer ...
Matthew
4
@Henning: Pouvez-vous développer ou fournir un lien concernant les inconvénients de la redirection ou de la canalisation dans un ssh interactif?
Isaac Kleinman
un peu en retard mais: ici vous n'avez pas besoin d'un pseudo terminal, donc utilisez -Tplutôt l' option .
Florian Castellane
6

Après avoir lu beaucoup de ces réponses, j'ai pensé partager ma solution résultante. Tout ce que j'ai ajouté est /bin/bashavant l'hérédoc et cela ne donne plus l'erreur.

Utilisez ceci:

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

Au lieu de cela (donne une erreur):

ssh user@machine <<'ENDSSH'
   hostname
ENDSSH

Ou utilisez ceci:

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

Au lieu de cela (donne une erreur):

ssh user@machine < run-command.sh

EXTRA :

Si vous voulez toujours une invite interactive à distance, par exemple si le script que vous exécutez à distance vous demande un mot de passe ou d'autres informations, car les solutions précédentes ne vous permettront pas de taper les invites.

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

Et si vous souhaitez également enregistrer toute la session dans un fichier logfile.log:

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

J'avais la même erreur sous Windows en utilisant emacs 24.5.1 pour me connecter à certains serveurs de l'entreprise via / ssh: user @ host. Ce qui a résolu mon problème était de définir la variable "tramp-default-method" sur "plink" et chaque fois que je me connecte à un serveur, j'oublie le protocole ssh. Vous devez avoir installé plink.exe de PuTTY pour que cela fonctionne.

Solution

  1. Mx personnaliser la variable (puis appuyez sur Entrée)
  2. tramp-default-method (puis appuyez à nouveau sur Entrée)
  3. Dans le champ de texte, placez plink, puis Appliquer et enregistrer le tampon
  4. Chaque fois que j'essaie d'accéder à un serveur distant, j'utilise maintenant Cxf / user @ host: puis j'entre le mot de passe. La connexion est maintenant correctement établie sous Emacs sous Windows avec mon serveur distant.
Miguel Rentes
la source
-3

ssh -t foobar @ localhost yourscript.pl

mxftw
la source
2
L'OP utilise -tégalement, mais dans leur scénario spécifique, cela ne suffit pas, ce qui a amené la question à commencer.
mklement0