Pseudo-terminal no será asignado porque stdin no es una terminal

Intento escribir un script de shell que crea algunos directorios en un servidor remoto y luego usa scp para copiar archivos desde mi máquina local al 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 recibo 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?

Pruebe ssh -t -t (o ssh -tt para abreviar) para forzar la asignación de pseudo-tty incluso si stdin no es un terminal.

Ver también: Finalización de sesión SSH ejecutada por script bash

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

También con la opción -T del manual

Desactivar la asignación pseudo-tty

Por la respuesta de zanco , no está proporcionando un comando remoto para ssh , dado cómo el shell analiza la línea de comando. Para resolver este problema, cambie la syntax de su invocación de comando ssh para que el comando remoto esté compuesto por una cadena sintácticamente correcta de varias líneas.

Hay una variedad de syntax que se pueden usar. Por ejemplo, dado que los comandos se pueden canalizar a bash y sh , y probablemente también a otros shells, la solución más simple es simplemente combinar la invocación del shell ssh 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 la ejecución de lo anterior sin /bin/bash dará como resultado la advertencia Pseudo-terminal will not be allocated because stdin is not a terminal . También tenga en cuenta que EOT está rodeado de comillas simples, de modo que bash reconoce el heredoc como un nowdoc , desactivando la interpolación de variables locales para que el texto del comando se pase tal cual 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 sobre /bin/bash aplica a lo anterior.

Otro enfoque válido es pasar el comando remoto de varias líneas como una cadena única, utilizando varias capas de interpolación de variables bash 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@server es analizado por bash, y se interpreta como el comando ssh , seguido de un argumento user@server para ser pasado al comando ssh

  2. " comienza una cadena interpolada, que cuando se complete, comprenderá un argumento para pasar al comando ssh , que en este caso será interpretado por ssh como el comando remoto para ejecutar como user@server

  3. $( comienza un comando para ser ejecutado, con la salida siendo capturada por la cadena interpolada circundante

  4. cat es un comando para generar los contenidos del archivo que sigue. La salida del cat pasará nuevamente a la cadena interpolada de captura

  5. << comienza un bash heredoc

  6. 'EOT' especifica que el nombre del heredoc es EOT. El EOT circundante de ' comillas simples especifica que el heredoc se debe analizar como un nowdoc , que es una forma especial de heredoc en la que el contenido no se interpola con bash, sino que se transmite en formato literal

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

  8. EOT finaliza el nowdoc, lo que da como resultado que se cree un archivo temporal nowdoc y se devuelve al comando cat llamante. cat emite el nowdoc y pasa la salida a la cadena interpolada de captura

  9. ) concluye el comando que se ejecutará

  10. " concluye la captura de la secuencia interpolada. El contenido de la cadena interpolada será devuelto a ssh como un argumento de línea de comando único, que ssh interpretará como el comando remoto para ejecutar como user@server

Si necesita evitar el uso de herramientas externas como cat , y no le molesta tener dos declaraciones en lugar de una, use la read incorporada 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}" 

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

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

Resolución : resultó que no había instalado el progtwig cliente y las utilidades de openssh. Debido a eso, 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.

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 para ssh mientras que stdin se redirige desde un documento aquí. Debido a la falta de un comando especificado como argumento, ssh primero espera una sesión de inicio de sesión interactiva (lo 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. Redirigir el stdin de ssh desde un documento aquí normalmente requiere un comando (como /bin/sh ) para ser especificado como un argumento para ssh – y en tal caso no se asignará pty en el host remoto de manera predeterminada.

Dado que no hay comandos para ser ejecutados a través de ssh que requieren la presencia de un tty / pty (como vim o top ), el -t cambiar a ssh es superfluo. Solo use ssh -T user@server < o ssh user@server /bin/bash < y la advertencia desaparecerá.

Si < no se escapa o se cotiza en una sola casilla (es decir, <<\EOT o <<'EOT' ) las variables dentro del documento aquí se expandirán por el shell local antes de que se ejecute ssh ... El efecto es que las variables dentro del documento aquí permanecerán vacías porque están definidas solo en el shell remoto.

Por lo tanto, si $REL_DIR debe ser accesible desde el shell local y definido en el shell remoto, $REL_DIR debe definirse fuera del documento aquí antes del comando ssh ( versión 1 a continuación); o, si se usa <<\EOT o <<'EOT' , la salida del comando ssh puede asignarse a REL_DIR si la única salida del comando ssh a stdout es generada por echo "$REL_DIR" dentro del escape / single -citado aquí documento ( 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 un argumento de comando a ssh -t user@server "$heredoc" ( versión 3 a continuación).

Y, por último, no sería mala idea comprobar si los directorios en el host remoto se crearon correctamente (ver: verificar 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 <&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" 

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

El mensaje de pseudo-terminal se debe a su -t que le pide a ssh que intente hacer que el entorno que ejecuta en la máquina remota parezca una terminal real de los progtwigs 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 de terminal especiales en adelante desde la máquina remota a su terminal actual en el extremo local.

¿Qué intentabas lograr con -t todos modos?

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

tl; dr:

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

    • '...' 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 usa un documento aquí :
    ssh jdoe@server <<'EOF' # Do NOT do this ... EOF

Pasar los comandos como un argumento funciona como está, y:

  • el problema con el pseudo-terminal ni siquiera surgirá.
  • no necesitará una instrucción de exit al final de sus comandos, porque la sesión saldrá automáticamente después de que los comandos hayan sido procesados.

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


Información de fondo opcional:

El mecanismo de ssh para aceptar comandos para ejecutar en el servidor de destino es un argumento de línea de comando : el operando final (argumento no opcional) acepta una cadena que contiene uno o más comandos de shell.

  • De forma predeterminada, estos comandos se ejecutan sin supervisión, en un shell no interactivo , sin el uso de un (pseudo) terminal (la opción -T es implí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 un aviso interactivo, puede solicitar explícitamente la creación de un pty (pseudo-tty) , un pseudo-terminal, que permite interactuar con la sesión remota, utilizando la opción -t ; p.ej:

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

    • Tenga en cuenta que el mensaje de read interactiva solo funciona correctamente con pty, por lo que se necesita la opción -t .

    • Usar un pty tiene un efecto secundario notable: stdout y stderr se combinan y ambos se informan a través de stdout ; en otras palabras: pierdes la distinción entre salida normal y 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, ssh crea un shell interactivo , incluso cuando envía comandos a través de stdin , que es donde comienza el problema:

  • Para un shell interactivo , ssh normalmente asigna 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 ssh 's 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 opción -t , cuyo propósito expreso es solicitar la creación de una pty, no es suficiente en este caso : recibirá la misma advertencia.

      • Curiosamente, debes duplicar la opción -t para forzar la creación de una pty: ssh -t -t ... o ssh -tt ... muestra que realmente, en serio .

      • Tal vez la razón para exigir 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 usa -tt , no funciona correctamente; la sesión se bloquea después de responder al mensaje de read :
        ssh -tt jdoe@server <<<'read -p "Enter something: "; echo "Entered: [$REPLY]"'


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

En caso de necesidad, utilice -T , y proporcione los comandos mediante stdin , con un comando de exit final, pero tenga en cuenta que si también necesita funciones interactivas, puede que no funcione el uso de -tt en lugar de -T .

Después de leer muchas de estas respuestas, pensé que compartiría mi solución resultante. Todo lo que agregué es /bin/bash antes del heredoc y ya no da el error.

Utilizar esta:

 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 una solicitud interactiva remota, por ejemplo, si la secuencia de comandos 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 las solicitudes.

 ssh -t user@machine "$( 

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

 ssh -t user@machine "$( 

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 establecer la variable “tramp-default-method” para “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. tramp-default-method (y luego presione Enter nuevamente)
  3. En el campo de texto ponga plink y luego Apply and Save the buffer
  4. Cada vez que bash acceder a un servidor remoto, ahora uso Cxf / user @ host: y luego ingreso la contraseña. La conexión ahora está hecha correctamente bajo Emacs en Windows a mi servidor remoto.

ssh -t foobar @ localhost yourscript.pl