El pseudo-terminal no se asignará porque stdin no es un terminal

345

Estoy tratando de escribir un script de shell que crea algunos directorios en un servidor remoto y luego usa scp para copiar archivos de mi máquina local en el control remoto. Esto es lo que tengo hasta ahora:

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

Cada vez que lo ejecuto me sale este mensaje:

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

Y el guión simplemente se cuelga para siempre.

Mi clave pública es de confianza en el servidor y puedo ejecutar todos los comandos fuera del script muy bien. ¿Algunas ideas?

Mateo
fuente
44
Simplemente puede especificar el terminal para usar comossh user@server /bin/bash <<EOT…
Buzut
3
@Buzut: Probablemente te refieres a shell , pero sí, especificar /bin/bashexplícitamente es una forma de evitar el problema.
mklement0
1
@ mklement0 de hecho, eso es lo que quise decir. Gracias por corregir eso;)
Buzut

Respuestas:

512

Intente ssh -t -t(o ssh -ttpara abreviar) forzar la asignación de pseudo-tty incluso si stdin no es una terminal.

Vea también: Terminar sesión SSH ejecutada por script bash

Desde la página de manual 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
fuente
21
Tengo un problema similar en un script que se ejecuta aquí. Agregué -t -t pero ahora recibo un nuevo error. "tcgetattr: ioctl inapropiado para dispositivo"
MasterZ
55
¿Por qué ssh -t -ty no ssh -tt? ¿Hay alguna diferencia que no sepa?
Jack
3
@MasterZ Lo mismo aquí. Sería bueno obtener una respuesta a estoInappropriate IOCtl for device
krb686
11
@Jack -tty -t -tson equivalentes; especificar args por separado o suavizarlos juntos se convierte en una cuestión de estilo / preferencia personal, y cuando se trata de eso, existen argumentos válidos para hacerlo de cualquier manera. pero realmente, es solo preferencia personal.
JDS
55
¿Qué significa cuando dice "incluso si ssh no tiene tty local"?
CMCDragonkai
192

También con opción -Tde manual

Deshabilitar la asignación de pseudo-tty

Emil Bojda
fuente
11
Esta respuesta necesita más puntos: es la respuesta correcta, y no demasiado larga como la respuesta de zanco, es absurdo que "oh, asigne un TTY de todos modos con -t -t" obtenga 107 puntos, cuando puede omitirlo por completo
respondió
2
Recién votado; funcionó mejor para mí que -t -t
Vincent Hiribarren
15
'-t -t' funciona, sin embargo con '-T' obtengo 'sudo: lo siento, debes tener un tty para ejecutar sudo'
Ivan Balashov
3
@nhed: la -ttopción parece mejor para aquellos de nosotros que realmente queremos un TTY y estamos recibiendo el mensaje de error del OP. Esta -Trespuesta es mejor si no necesita el TTY.
ErichBSchulz
3
@nhed, claro, pero estoy seguro de que otros como yo llegaron aquí buscando en Google el error en el mensaje de error. No parece irrazonable dejar en claro cómo otros googlers deberían elegir entre 2 respuestas competitivas. o tal vez no. no sé
ErichBSchulz
91

Según la respuesta de zanco , no está proporcionando un comando remoto ssh, dada la forma en que el shell analiza la línea de comando. Para resolver este problema, cambie la sintaxis de la sshinvocación de su comando para que el comando remoto esté compuesto por una cadena de varias líneas sintácticamente correcta.

Hay una variedad de sintaxis que se pueden usar. Por ejemplo, dado que los comandos se pueden canalizar bashy sh, y probablemente también en otros shells, la solución más simple es combinar la sshinvocación de shell con heredocs:

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

Tenga en cuenta que ejecutar lo anterior sin /bin/bash generará la advertencia Pseudo-terminal will not be allocated because stdin is not a terminal. También tenga en cuenta que EOTestá rodeado de comillas simples, por lo que bashreconoce el heredoc como un nowdoc , desactivando la interpolación de variables locales para que el texto del comando se pase como está a ssh.

Si eres fanático de las tuberías, puedes reescribir lo anterior de la siguiente manera:

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

La misma advertencia se /bin/bashaplica a lo anterior.

Otro enfoque válido es pasar el comando remoto de varias líneas como una sola cadena, utilizando múltiples capas de bashinterpolación variable de la siguiente manera:

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

La solución anterior soluciona este problema de la siguiente manera:

  1. ssh user@serverbash lo analiza y se interpreta como el sshcomando, seguido de un argumento user@serverque se debe pasar al sshcomando

  2. "comienza una cadena interpolada, que cuando se complete, comprenderá un argumento que se pasará al sshcomando, que en este caso se interpretará sshcomo el comando remoto para ejecutar comouser@server

  3. $( comienza un comando que se ejecutará, con la salida capturada por la cadena interpolada circundante

  4. cates un comando para generar el contenido del archivo que sigue. La salida de catse devolverá a la cadena interpolada de captura

  5. <<comienza un golpe heredoc

  6. 'EOT'especifica que el nombre del heredoc es EOT. Las comillas simples que 'rodean a EOT especifican que el heredoc debe analizarse como un nowdoc , que es una forma especial de heredoc en la que los contenidos no se interpolan por bash, sino que se transmiten en formato literal

  7. Cualquier contenido que se encuentre entre <<'EOT'y <newline>EOT<newline>se agregará a la salida de nowdoc

  8. EOTtermina el nowdoc, lo que da como resultado que se cree un archivo temporal de nowdoc y se lo devuelva al catcomando de llamada . catgenera el nowdoc y devuelve el resultado a la cadena interpolada de captura

  9. ) concluye el comando a ejecutar

  10. "concluye la cadena de captura interpolada. El contenido de la cadena interpolada se devolverá sshcomo un argumento de línea de comando único, que sshse interpretará como el comando remoto para ejecutar comouser@server

Si necesita evitar el uso de herramientas externas como cat, y no le importa tener dos declaraciones en lugar de una, use el readincorporado con un heredoc para generar el 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
fuente
¡+1 año y medio después! :) explicación realmente clara. bien hecho
yaroslavTir
1
Para mí (y de ninguna manera soy una persona "DevOps") esto es lo que funcionó (estoy usando Jenkins). También probé las sugerencias "-t -t" y "-T", pero me encontré con problemas de flujo abierto y problemas de no ejecución.
Ryan Crews
2 años después, realmente útil
jack-nie
+1 Explicación impresionante. Pero si hace que el último fragmento de código (IFS = '' *) sea una función para ejecutar comandos remotos, ¿cómo pasaría $ 1 al IFS = '' *? Parece que reconoce el contenido de $ 1 en absoluto.
Cristian Matthias Ambæk
1
@ mklement0 Claro, puede escapar de las cadenas según sea necesario, pero ¿quién quiere la molestia de tener que modificar las declaraciones de script que podría estar copiando y pegando desde otro script de shell o stackoverflow para el caso? Además, la elegancia de la catsolución es que se logra en una sola declaración (aunque compuesta), y no da como resultado que las variables temporales contaminen el entorno de shell.
Dejay Clayton
63

Estoy agregando esta respuesta porque resolvió un problema relacionado que estaba teniendo con el mismo mensaje de error.

Problema : instalé cygwin en Windows y recibí este error:Pseudo-terminal will not be allocated because stdin is not a terminal

Resolución : Resulta que me había no instalado el programa cliente OpenSSH y los servicios públicos. Debido a que Cygwin estaba usando la implementación de Windows de ssh, no la versión de Cygwin. La solución fue instalar el paquete openssh cygwin.

Andrew Prock
fuente
10
Para mí resultó que fue otra implementación de SSH en la RUTA:$ which ssh/cygdrive/c/Program Files (x86)/Git/bin/ssh
FelixJongleur42
e instalar opensshesto puede ser el camino a seguir para Windows: superuser.com/a/301026/260710
Andreas Dietrich
Esta solución me funciona. Después de instalar openssh, el problema desapareció.
jdhao
34

El mensaje de advertencia Pseudo-terminal will not be allocated because stdin is not a terminal.se debe al hecho de que no se especifica ningún comando sshmientras stdin se redirige desde un documento aquí. Debido a la falta de un comando especificado como argumento, sshprimero se espera una sesión de inicio de sesión interactiva (que requeriría la asignación de una pty en el host remoto) pero luego debe darse cuenta de que su stdin local no es tty / pty. La redirección sshde la entrada estándar de un documento aquí normalmente requiere /bin/shque se especifique un comando (como ) como argumento para ssh, y en tal caso no se asignará pty en el host remoto de forma predeterminada.

Como no hay comandos para ejecutar sshque requieran la presencia de tty / pty (como vimo top), el -tcambio a sshes superfluo. Simplemente use ssh -T user@server <<EOT ...o ssh user@server /bin/bash <<EOT ...y la advertencia desaparecerá.

Si <<EOFno se escapa o las variables entre comillas simples (es decir, <<\EOTo <<'EOT') dentro del documento aquí serán expandidas por el shell local antes de ejecutarse ssh .... El efecto es que las variables dentro del documento aquí permanecerán vacías porque se definen solo en el shell remoto.

Por lo tanto, si $REL_DIRdebe ser accesible tanto por el shell local como definido en el shell remoto, $REL_DIRdebe definirse fuera del documento aquí antes del sshcomando ( versión 1 a continuación); o, si se usa <<\EOTo <<'EOT', sshse puede asignar REL_DIRla salida del sshcomando si la única salida del comando a stdout se genera echo "$REL_DIR"dentro del documento escapado / entre comillas simples ( versión 2 a continuación).

Una tercera opción sería almacenar el documento aquí en una variable y luego pasar esta variable como argumento de comando a ssh -t user@server "$heredoc"( versión 3 a continuación).

Y, por último, pero no menos importante, no sería una mala idea verificar si los directorios en el host remoto se crearon con éxito (consulte: verifique si el archivo existe en el host remoto con 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
fuente
55
Gracias por la explicación muy detallada sobre lo que significa este error y por qué aparece cuando se usa un heredoc, esto me ayudó a entenderlo en lugar de solucionarlo.
Dan
29

Toda la información relevante está en las respuestas existentes, pero permítame intentar un resumen pragmático :

tl; dr:

  • DEBE pasar los comandos para ejecutar usando un argumento de línea de comandos :
    ssh jdoe@server '...'

    • '...' las cadenas pueden abarcar varias líneas, por lo que puede mantener su código legible incluso sin el uso de un documento aquí:
      ssh jdoe@server ' ... '
  • NO pase los comandos a través de stdin , como es el caso cuando utiliza un documento aquí :
    ssh jdoe@server <<'EOF' # Do NOT do this ... EOF

Pasar los comandos como argumento funciona tal cual y:

  • El problema con el pseudo-terminal ni siquiera surgirá.
  • no necesitará una exitdeclaración al final de sus comandos, porque la sesión se cerrará automáticamente después de que se hayan procesado los comandos.

En resumen: pasar comandos a través de stdin es un mecanismo que está en desacuerdo con sshel diseño y causa problemas que luego deben solucionarse.
Sigue leyendo, si quieres saber más.


Información de antecedentes opcional:

sshEl mecanismo para aceptar comandos para ejecutar en el servidor de destino es un argumento de línea de comandos : el operando final (argumento sin opción) acepta una cadena que contiene uno o más comandos de shell.

  • De manera predeterminada, estos comandos se ejecutan desatendidos, en un shell no interactivo , sin el uso de un (pseudo) terminal (opción -Timplícita), y la sesión finaliza automáticamente cuando el último comando finaliza el procesamiento.

  • En el caso de que sus comandos requieran la interacción del usuario , como responder a una solicitud interactiva, puede solicitar explícitamente la creación de un pty (pseudo-tty) , un pseudo terminal, que permita interactuar con la sesión remota, utilizando la -topción; p.ej:

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

    • Tenga en cuenta que la readsolicitud interactiva solo funciona correctamente con una pty, por lo que -tse necesita la opción.

    • El uso de una pty tiene un efecto secundario notable: stdout y stderr se combinan y ambos se informan a través de stdout ; en otras palabras: pierde la distinción entre salida regular y salida de error; p.ej:

      • 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 ausencia de este argumento, sshcrea un shell interactivo , incluso cuando envía comandos a través de stdin , que es donde comienza el problema:

  • Para un shell interactivo , sshnormalmente asigna un pty (pseudo-terminal) por defecto, excepto si su stdin no está conectado a un terminal (real).

    • Enviar comandos a través de stdin significa que sshel stdin ya no está conectado a un terminal, por lo que no se crea pty, y ssh le advierte en consecuencia :
      Pseudo-terminal will not be allocated because stdin is not a terminal.

    • Incluso la -topción, cuyo propósito expreso es solicitar la creación de una pty, no es suficiente en este caso : obtendrá la misma advertencia.

      • Curiosamente, debe duplicar la -topción para forzar la creación de una empanada: ssh -t -t ...o ssh -tt ...muestra que realmente lo dice en serio .

      • Quizás la razón para requerir este paso tan deliberado es que las cosas pueden no funcionar como se esperaba . Por ejemplo, en macOS 10.12, el equivalente aparente del comando anterior, que proporciona los comandos a través de stdin y el uso -tt, no funciona correctamente; la sesión se atasca después de responder a la readsolicitud:
        ssh -tt jdoe@server <<<'read -p "Enter something: "; echo "Entered: [$REPLY]"'


En el improbable caso de que los comandos que desea pasar como argumento hagan que la línea de comandos sea demasiado larga para su sistema (si se acerca su longitud getconf ARG_MAX, consulte este artículo ), considere copiar primero el código al sistema remoto en forma de script ( usando, por ejemplo, scp), y luego enviar un comando para ejecutar ese script.

En caso de necesidad , utilice -Ty proporcione los comandos a través de stdin , con un exitcomando final , pero tenga en cuenta que si también necesita funciones interactivas, el uso -tten lugar de -Tpuede no funcionar.

mklement0
fuente
1
Esto funcionó perfectamente, la forma -tt estaba haciendo que la terminal mostrara cada comando enviado a través de ssh.
Mark E
1
"duplicar la opción -t para forzar la creación de una pty: ssh -t -t ... o ssh -tt ... muestra que realmente lo dices en serio". - ja, ja - gracias es gracioso pero serio también - No puedo entender la doble t, todo lo que sé es que si pongo una sola t no funciona
DavidC
22

No sé de dónde viene el bloqueo, pero la redirección (o canalización) de comandos a un ssh interactivo es, en general, una receta para los problemas. Es más robusto usar el estilo de comando para ejecutar como último argumento y pasar el script en la línea 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'

(Todo en un 'argumento de línea de comandos multilínea delimitado por gigantes ).

El mensaje de pseudo-terminal se debe a -tque le pide a ssh que intente hacer que el entorno que ejecuta en la máquina remota parezca un terminal real para los programas que se ejecutan allí. Su cliente ssh se niega a hacerlo porque su propia entrada estándar no es un terminal, por lo que no tiene forma de pasar las API especiales del terminal en adelante desde la máquina remota a su terminal real en el extremo local.

¿Qué estabas tratando de lograr de -ttodos modos?

hmakholm dejó a Monica
fuente
1
La opción -t fue un intento de solucionar el problema del terminal Psuedo (no funcionó). Probé su solución, que se deshizo de la cosa terminal de pseudo pero ahora se cuelga ...
Mateo
44
@Henning: ¿Puede elaborar o proporcionar un enlace sobre las desventajas de redirigir o canalizar en un ssh interactivo?
Isaac Kleinman
un poco tarde pero: aquí no necesitas un pseudo terminal, así que usa la opción en su -Tlugar.
Florian Castellane
6

Después de leer muchas de estas respuestas, pensé en compartir mi solución resultante. Todo lo que agregué está /bin/bashantes del heredoc y ya no da el error.

Utilizar este:

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

En lugar de esto (da error):

ssh user@machine <<'ENDSSH'
   hostname
ENDSSH

O usa esto:

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

En lugar de esto (da error):

ssh user@machine < run-command.sh

EXTRA :

Si aún desea un mensaje interactivo remoto, por ejemplo, si el script que está ejecutando de forma remota le solicita una contraseña u otra información, porque las soluciones anteriores no le permitirán escribir en los mensajes.

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

Y si también desea registrar toda la sesión en un archivo logfile.log:

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

Estaba teniendo el mismo error en Windows usando emacs 24.5.1 para conectarme a algunos servidores de la compañía a través de / ssh: user @ host. Lo que resolvió mi problema fue configurar la variable "método predeterminado de vagabundo" en "plink" y cada vez que me conecto a un servidor omito el protocolo ssh. Debe tener instalado Plink.exe de PuTTY para que esto funcione.

Solución

  1. Mx personalizar-variable (y luego presionar Enter)
  2. método predeterminado-vagabundo (y luego presione Enter nuevamente)
  3. En el campo de texto, coloque plink y luego aplique y guarde el búfer
  4. Cada vez que intento acceder a un servidor remoto, ahora uso Cxf / user @ host: y luego ingreso la contraseña. La conexión ahora se realiza correctamente bajo Emacs en Windows a mi servidor remoto.
Miguel Rentes
fuente
-3

ssh -t foobar @ localhost yourscript.pl

mxftw
fuente
2
El OP -ttambién lo usa , pero en su escenario específico eso no es suficiente, lo que provocó la pregunta para comenzar.
mklement0