web-dev-qa-db-fra.com

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

J'essaie d'écrire un script shell qui crée des répertoires sur un serveur distant, puis utilise scp pour copier des fichiers de mon ordinateur local sur la machine distante. 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 le lance, 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?

300
Matthew

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

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

De la page de manuel 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.
445
carok

Aussi avec l'option -T de manuel

Désactiver l'allocation de pseudo-tty

163
Emil Bojda

Per la réponse de zanco , vous ne fournissez pas de commande à distance à ssh, compte tenu de la manière dont le shell analyse la ligne de commande. Pour résoudre ce problème, modifiez la syntaxe de votre invocation de commande ssh afin que la commande distante soit composée d'une chaîne multiligne correcte sur le plan syntaxique.

Diverses syntaxes peuvent être utilisées. Par exemple, étant donné que les commandes peuvent être acheminées dans bash et sh, et probablement d'autres shells également, la solution la plus simple consiste simplement à combiner ssh invocation de 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 que EOT est entouré de guillemets simples, de sorte que bash reconnaisse l’hérédoc comme un nowdoc , désactivant le mode local. interpolation variable afin que le texte de la commande soit passé tel quel à ssh.

Si vous êtes fan de pipes, 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 valable consiste à transmettre la commande à plusieurs lignes à distance en une seule chaîne, en utilisant plusieurs couches d’interpolation de variable bash 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@server est analysé par bash et interprété comme étant la commande ssh, suivi d'un argument user@server à transmettre à la commande ssh.

  2. " commence une chaîne interpolée qui, une fois terminée, comprendra un argument à transmettre à la commande ssh qui, dans ce cas, sera interprété par ssh comme étant la commande à distance à exécuter. comme user@server

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

  4. cat est une commande permettant de générer le contenu du fichier suivant. La sortie de cat sera renvoyée dans la chaîne interpolée de capture.

  5. << commence une bash heredoc

  6. 'EOT' spécifie que le nom de l'hérédoc est EOT. Les guillemets simples ' entourant EOT spécifient que l'hérédoc doit être analysé comme un nowdoc , qui est une forme spéciale d'hérédoc dans laquelle le contenu ne pas être 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 de nowdoc.

  8. EOT met fin à la commande nowdoc, ce qui entraîne la création d'un fichier temporaire nowdoc qui est ensuite renvoyé à la commande appelante cat. cat renvoie le nowdoc et renvoie le résultat à la chaîne interpolée de capture

  9. ) conclut la commande à exécuter

  10. " termine la capture de la chaîne interpolée. Le contenu de la chaîne interpolée sera renvoyé à ssh sous la forme d'un argument de ligne de commande unique, qui ssh sera interprété comme la commande à distance à exécuter sous la forme user@server

Si vous devez éviter d'utiliser des outils externes tels que cat, et que cela ne vous dérange pas d'avoir deux instructions à la place d'une, utilisez le read intégré à un heredoc 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}"
76
Dejay Clayton

J'ajoute cette réponse car elle résout un problème connexe que je rencontrais 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 j’avais pas installé le programme client openssh et les utilitaires. À cause de cela, cygwin utilisait l'implémentation Windows de ssh, pas la version de cygwin. La solution consistait à installer le paquet openssh cygwin.

57
Andrew Prock

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 pour ssh alors que stdin est redirigé depuis un document here. En raison de l'absence d'une commande spécifiée en tant qu'argument, ssh s'attend d'abord à une session de connexion interactive (ce qui nécessiterait l'attribution 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 du stdin de ssh à partir d'un document here nécessite normalement qu'une commande (telle que /bin/sh) soit spécifiée en tant qu'argument vers ssh - et dans ce cas, aucun pty ne sera alloué à distance. Hôte par défaut.

Puisqu'il n'y a pas de commandes à exécuter via ssh nécessitant la présence d'un tty/pty (tel que vim ou top), le commutateur -t sur ssh est superflu. Utilisez simplement ssh -T user@server <<EOT ... ou ssh user@server /bin/bash <<EOT ... et l'avertissement disparaîtra.

Si <<EOF n'est pas échappé ni entre guillemets simples (i. E. <<\EOT ou <<'EOT'), les variables de ce document seront développées par le shell local avant l'exécution de ssh .... L'effet est que les variables à l'intérieur du document here resteront vides car elles sont définies uniquement dans le shell distant.

Donc, si $REL_DIR doit être à la fois accessible par le shell local et défini dans le shell distant, $REL_DIR doit être défini en dehors du document ici avant la commande ssh ( version 1 ci-dessous); ou, si <<\EOT ou <<'EOT' est utilisé, la sortie de la commande ssh peut être affectée à REL_DIR si la seule sortie de la commande ssh à la sortie standard est généré par echo "$REL_DIR" dans le document échappé/simple-cité ici ( version 2 ci-dessous).

Une troisième option consisterait à stocker le document ici dans une variable, puis à transmettre cette variable en tant qu'argument de commande à ssh -t user@server "$heredoc" ( version 3 ci-dessous ).

Et, dernier point, ce ne serait pas une mauvaise idée de vérifier si les répertoires de l'hôte distant ont été créés avec succès (voir: vérifiez 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"
30
zanco

Toutes les informations pertinentes se trouvent dans les réponses existantes, mais laissez-moi essayer un résumé pragmatique :

tl; dr:

  • NE PAS transmettre 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, ce qui vous permet de garder votre code lisible même sans utiliser de document here:
      ssh jdoe@server ' ... '
  • NE PAS passer les commandes via stdin, comme c'est le cas lorsque vous utilisez un here-document =:
    ssh jdoe@server <<'EOF' # Do NOT do this ... EOF

Passer les commandes en tant qu'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 instruction exit à la fin de vos commandes, car la session se fermera automatiquement une fois les commandes traitées.

En bref: le fait de passer des commandes via stdin est un mécanisme en contradiction avec la conception de ssh et pose des problèmes qui doivent ensuite être contournés.
Continuez votre lecture si vous voulez en savoir plus.


Informations générales facultatives:

Le mécanisme de ssh pour accepter les commandes à exécuter sur le serveur cible est un argument de ligne de commande: l'opérande final (argument non-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 (l'option -T est implicite) et la session se termine automatiquement à la fin du traitement de la dernière commande.

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

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

    • Notez que l'invite interactive read ne fonctionne correctement qu'avec un pty, l'option -t est donc nécessaire.

    • L'utilisation d'un pty a un effet secondaire notable: stdout et stderr sont combinés et tous les deux sont signalés via stdout; en d'autres termes: vous perdez la distinction entre la sortie régulière et la 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, ssh crée un interactif Shell - y compris lorsque vous envoyez des commandes via - stdin, où commence le problème:

  • Pour un shell interactif, ssh alloue normalement un pseudo-terminal (pseudo-terminal) par défaut, sauf si son stdin n'est pas connecté à un terminal (réel).

    • L'envoi de commandes via stdin signifie que le _ stdin de ssh n'est plus connecté à un terminal. Ainsi, no pty est créé et ssh - met en garde vous en conséquence :
      Pseudo-terminal will not be allocated because stdin is not a terminal.

    • Même l'option -t dont le but explicite est de demande créer un pty, est pas assez dans ce cas: vous recevrez le même avertissement.

      • Curieusement, vous devez alors double l'option -t pour forcer la création d'un pty: ssh -t -t ... ou ssh -tt ... montre que vous signifie vraiment.

      • Peut-être que la raison pour exiger cette étape très délibérée est que les choses pourraient ne pas fonctionner comme prév. Par exemple, sur macOS 10.12, l'équivalent apparent de la commande ci-dessus, qui fournit les commandes via stdin et utilise -tt, fonctionne pas fonctionne correctement; la session est bloquée après avoir répondu à l'invite read:
        ssh -tt jdoe@server <<<'read -p "Enter something: "; echo "Entered: [$REPLY]"'


Dans le cas peu probable où les commandes que vous voulez passer en argument, la ligne de commande serait trop longue pour votre système (si sa longueur approche getconf ARG_MAX - voir cet article ), envisagez de copier le code. au 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 -T, et fournissez les commandes via stdin, avec une dernière commande exit, mais notez que si vous avez également besoin de fonctionnalités interactives, utilisez -tt au lieu de -T peut ne pas fonctionner.

24
mklement0

Je ne sais pas d'où vient le blocage, mais la redirection (ou la tuyauterie) des commandes dans un ssh interactif est généralement une recette pour résoudre les problèmes. Il est plus robuste d'utiliser le style commande à exécution en 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é par '-).

Le message pseudo-terminal provient de votre -t qui demande à ssh de faire en sorte que l'environnement exécuté sur la machine distante ressemble à un terminal pour les programmes qui y sont exécutés. Votre client ssh refuse de le faire car son entrée standard propre n’est pas un terminal. Il n’a donc aucun moyen de transmettre les API spéciales du terminal de la machine distante à votre terminal actuel, au niveau local.

Qu'est-ce que vous essayiez de faire avec -t de toute façon?

21
Henning Makholm

Après avoir lu beaucoup de ces réponses, j'ai pensé partager ma solution résultante. Tout ce que j'ai ajouté, c'est /bin/bash avant 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 souhaitez toujours une invite interactive à distance, par exemple, si le script que vous exécutez à distance vous invite à entrer un mot de passe ou d'autres informations, car les solutions précédentes ne vous permettent pas de saisir 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
5
Wadih M.

J'avais la même erreur sous Windows en utilisant emacs 24.5.1 pour se connecter à certains serveurs de la société via/ssh: utilisateur @ hôte. Ce qui a résolu mon problème, c’était de régler la variable "tramp-default-method" sur "plink" et chaque fois que je me connecte à un serveur, j’ignore le protocole ssh. Pour que cela fonctionne, vous devez avoir le fichier plink.exe de PuTTY.

Solution

  1. M-x personnaliser la variable (puis appuyez sur Entrée)
  2. tramp-default-method (puis appuyez à nouveau sur Entrée)
  3. Sur le champ de texte mettez plink puis Appliquer et sauvegarder le tampon
  4. À chaque fois que j'essaie d'accéder à un serveur distant, j'utilise maintenant C-x-f/user @ Host: et saisis ensuite le mot de passe. La connexion est maintenant correctement établie sous Emacs sous Windows sur mon serveur distant.
0
Miguel Rentes