Lo pseudo-terminale non verrà allocato perché stdin non è un terminale


345

Sto provando a scrivere uno script di shell che crea alcune directory su un server remoto e quindi usa scp per copiare i file dalla mia macchina locale sul telecomando. Ecco cosa ho finora:

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

Ogni volta che lo eseguo ricevo questo messaggio:

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

E la sceneggiatura si blocca per sempre.

La mia chiave pubblica è attendibile sul server e posso eseguire bene tutti i comandi al di fuori dello script. Qualche idea?


4
Puoi semplicemente specificare il terminale da usare comessh user@server /bin/bash <<EOT…
Buzut,

3
@Buzut: Probabilmente intendi shell , ma, sì, specificare /bin/bashesplicitamente è un modo per evitare il problema.
mklement0,

1
@ mklement0 in effetti, questo è ciò che intendevo. Grazie per
averlo

Risposte:


512

Prova ssh -t -t(o ssh -ttin breve) a forzare l'allocazione pseudo-tty anche se stdin non è un terminale.

Vedi anche: Terminare la sessione SSH eseguita dallo script bash

Dalla manpage di 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.

21
Sto riscontrando un problema simile in uno script che viene eseguito qui. Ho aggiunto -t -t ma ora sto ricevendo un nuovo errore. "tcgetattr: ioctl inappropriato per dispositivo"
MasterZ

5
Perché ssh -t -te no ssh -tt? C'è una differenza di cui non sono a conoscenza?
Jack,

3
@MasterZ Stessa cosa qui. Sarebbe bello avere una risposta a questoInappropriate IOCtl for device
krb686

11
@ Jack -tte -t -tsono equivalenti; specificare argomenti separatamente o smoosh insieme diventa una questione di stile / preferenza personale, e quando si tratta di esso, ci sono argomenti validi per farlo in entrambi i modi. ma davvero, è solo una preferenza personale.
JDS,

5
Cosa significa quando dice "anche se ssh non ha tty locale"?
CMCDragonkai,

192

Anche con opzione -Tdal manuale

Disabilita l'allocazione pseudo-tty


11
Questa risposta ha bisogno di più punti - è la risposta giusta, e non troppo a lungo come la risposta di Zanco, è assurdo che "oh alloca un TTY comunque con -t -t" ottiene 107 punti, quando puoi semplicemente saltarlo del tutto
nhed

2
Appena votato; ha funzionato meglio per me rispetto a -t -t
Vincent Hiribarren,

15
'-t -t' funziona, tuttavia con '-T' ottengo 'sudo: scusa, devi avere una tty per eseguire sudo'
Ivan Balashov,

3
@nhed: l' -ttopzione appare migliore per quelli di noi che vogliono effettivamente un TTY e ricevono il messaggio di errore dell'OP. Questa -Trisposta è migliore se non hai bisogno del TTY.
ErichBSchulz,

3
@nhed - certo - ma sono sicuro che altri come me sono arrivati ​​da Google cercando l'errore nel messaggio di errore. Non sembra irragionevole chiarire come altri googler dovrebbero scegliere tra 2 risposte in competizione. o forse no. non lo so
ErichBSchulz,

91

Per la risposta di zanco , non stai fornendo un comando remoto a ssh, dato come la shell analizza la riga di comando. Per risolvere questo problema, modificare la sintassi sshdell'invocazione del comando in modo che il comando remoto sia composto da una stringa multilinea sintatticamente corretta.

Esistono varie sintassi che possono essere utilizzate. Ad esempio, poiché i comandi possono essere reindirizzati bashe sh, e probabilmente anche altre shell, la soluzione più semplice è semplicemente combinare l' sshinvocazione della 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

Si noti che l'esecuzione di quanto sopra senza /bin/bash comporterà l'avviso Pseudo-terminal will not be allocated because stdin is not a terminal. Nota inoltre che EOTè racchiuso tra virgolette singole, in modo da bashriconoscere l'eredoc come un nowdoc , disattivando l'interpolazione della variabile locale in modo che il testo del comando venga passato così com'è ssh.

Se sei un fan delle pipe, puoi riscrivere quanto sopra come segue:

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

Lo stesso avvertimento /bin/bashvale per quanto sopra.

Un altro approccio valido è passare il comando remoto multilinea come una singola stringa, usando più livelli di bashinterpolazione variabile come segue:

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

La soluzione sopra risolve questo problema nel modo seguente:

  1. ssh user@serverviene analizzato da bash e viene interpretato come il sshcomando, seguito da un argomento user@serverda passare al sshcomando

  2. "inizia una stringa interpolata, che una volta completata comprenderà un argomento da passare al sshcomando, che in questo caso verrà interpretato sshcome il comando remoto da eseguire comeuser@server

  3. $( inizia un comando da eseguire, con l'output catturato dalla stringa interpolata circostante

  4. catè un comando per generare il contenuto di qualunque file segue. L'output di catverrà passato nuovamente nella stringa interpolata di acquisizione

  5. <<inizia un branco ereditario

  6. 'EOT'specifica che il nome dell'eredità è EOT. Le singole virgolette che 'circondano EOT specificano che l'ereditarietà dovrebbe essere analizzata come nowdoc , che è una forma speciale di ereditarietà in cui i contenuti non vengono interpolati da bash, ma piuttosto trasmessi in formato letterale

  7. Qualsiasi contenuto riscontrato tra <<'EOT'e <newline>EOT<newline>verrà aggiunto all'output nowdoc

  8. EOTtermina nowdoc, creando un file temporaneo nowdoc che viene restituito al catcomando chiamante . catgenera l'outdoc e restituisce l'output alla stringa interpolata di acquisizione

  9. ) conclude il comando da eseguire

  10. "conclude la stringa interpolata di acquisizione. Il contenuto della stringa interpolata verrà restituito sshcome singolo argomento della riga di comando, che sshinterpreterà come il comando remoto da eseguire comeuser@server

Se devi evitare di usare strumenti esterni come cat, e non ti dispiace avere due istruzioni invece di una, usa il readbuilt-in con un heredoc per generare il 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}"

+1 un anno e mezzo dopo! :) spiegazione davvero chiara. ben fatto
yaroslavTir

1
Per me (e non sono affatto una persona "DevOps") questo è ciò che ha funzionato (sto usando Jenkins). Ho anche provato i suggerimenti "-t -t" e "-T", ma ho riscontrato problemi di flusso aperto e problemi di non esecuzione.
Ryan Crews,

2 anni dopo, davvero utile
jack-nie

+1 Spiegazione straordinaria. Ma se rendessi l'ultimo frammento di codice (IFS = '' *) una funzione per eseguire comandi remoti, come passeresti $ 1 in IFS = '' *? Sembra docente riconoscere il contenuto di $ 1 a tutti.
Cristian Matthias Ambæk,

1
@ mklement0 Certo, puoi sfuggire alle stringhe secondo necessità, ma chi vuole il fastidio di dover modificare le istruzioni di script che potresti semplicemente copiare e incollare da un altro script di shell o stackoverflow per quella materia? Inoltre, l'eleganza della catsoluzione è che viene realizzata in una singola istruzione (sebbene composta) e non provoca variabili temporanee che inquinano l'ambiente della shell.
Dejay Clayton,

63

Sto aggiungendo questa risposta perché ha risolto un problema correlato con lo stesso messaggio di errore.

Problema : avevo installato Cygwin su Windows e stava ottenendo questo errore:Pseudo-terminal will not be allocated because stdin is not a terminal

Risoluzione : Si scopre che avevo non installato il programma client openssh e le utility. Per questo motivo Cygwin utilizzava l'implementazione di Windows di ssh, non la versione di Cygwin. La soluzione era installare il pacchetto cyshwin di openssh.


10
Per me si è scoperto che era un'altra implementazione ssh nel PERCORSO:$ which ssh/cygdrive/c/Program Files (x86)/Git/bin/ssh
FelixJongleur42

e installarlo opensshpotrebbe essere la strada da percorrere per Windows: superuser.com/a/301026/260710
Andreas Dietrich

Questa soluzione funziona per me. Dopo aver installato openssh, il problema è scomparso.
jdhao,

34

Il messaggio di avviso Pseudo-terminal will not be allocated because stdin is not a terminal.è dovuto al fatto che non è stato specificato alcun comando sshmentre stdin viene reindirizzato da un documento qui. A causa della mancanza di un comando specificato come argomento, sshprima si aspetta una sessione di login interattiva (che richiederebbe l'assegnazione di un pty sull'host remoto) ma poi deve rendersi conto che il suo stdin locale non è tty / pty. Il reindirizzamento dello sshstdin da un documento qui richiede normalmente un comando (come /bin/sh) da specificare come argomento per ssh- e in tal caso nessun pty verrà allocato sull'host remoto per impostazione predefinita.

Poiché non vi sono comandi da eseguire tramite quelli sshche richiedono la presenza di un tty / pty (come vimo top), il -tpassaggio a sshè superfluo. Basta usare ssh -T user@server <<EOT ...o ssh user@server /bin/bash <<EOT ...e l'avvertimento sparirà.

Se <<EOFnon viene eseguito l' escaping <<\EOTo le <<'EOT'variabili a virgoletta singola (ovvero o ) all'interno del documento qui verranno espanse dalla shell locale prima dell'esecuzione ssh .... L'effetto è che le variabili all'interno del documento qui rimarranno vuote perché sono definite solo nella shell remota.

Quindi, se $REL_DIRdeve essere sia accessibile dalla shell locale sia definito nella shell remota, $REL_DIRdeve essere definito all'esterno del documento qui prima del sshcomando ( versione 1 di seguito); oppure, se <<\EOTo <<'EOT'viene utilizzato, l'output del sshcomando può essere assegnato REL_DIRse l'unico output del sshcomando su stdout è generato echo "$REL_DIR"dall'interno del documento con escape / virgolette singole ( versione 2 in basso).

Una terza opzione sarebbe quella di memorizzare il documento here in una variabile e quindi passare questa variabile come argomento di comando a ssh -t user@server "$heredoc"( versione 3 di seguito).

E, ultimo ma non meno importante, non sarebbe una cattiva idea verificare se le directory sull'host remoto sono state create correttamente (vedi: controlla se il file esiste sull'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"

5
Grazie per la spiegazione molto dettagliata sul significato di questo errore e sul motivo per cui appare quando si usa un heredoc, questo mi ha aiutato a comprenderlo piuttosto che aggirare.
Dan,

29

Tutte le informazioni pertinenti si trovano nelle risposte esistenti, ma lasciami tentare un sommario pragmatico :

tl; dr:

  • Passare i comandi per eseguire utilizzando un argomento della riga di comando :
    ssh jdoe@server '...'

    • '...' le stringhe possono estendersi su più righe, quindi puoi mantenere il tuo codice leggibile anche senza l'uso di un documento qui:
      ssh jdoe@server ' ... '
  • NON passare i comandi tramite stdin , come nel caso di un documento qui :
    ssh jdoe@server <<'EOF' # Do NOT do this ... EOF

Passare i comandi come argomento funziona così com'è e:

  • il problema con lo pseudo-terminale non sorgerà nemmeno.
  • non avrai bisogno di una exitdichiarazione alla fine dei tuoi comandi, perché la sessione uscirà automaticamente dopo che i comandi sono stati elaborati.

In breve: passare comandi tramite stdin è un meccanismo che è in contrasto con sshil design e causa problemi che devono quindi essere risolti.
Continua a leggere, se vuoi saperne di più.


Informazioni di base opzionali:

sshIl meccanismo per accettare i comandi da eseguire sul server di destinazione è un argomento da riga di comando : l'operando finale (argomento non opzionale) accetta una stringa contenente uno o più comandi shell.

  • Per impostazione predefinita, questi comandi vengono eseguiti incustoditi, in una shell non interattiva , senza l'uso di un terminale (pseudo) (l'opzione -Tè implicita) e la sessione termina automaticamente quando l'ultimo comando termina l'elaborazione.

  • Nel caso in cui i comandi richiedano l'interazione dell'utente , come la risposta a un prompt interattivo, è possibile richiedere esplicitamente la creazione di un pty (pseudo-tty) , uno pseudo terminale, che consente l'interazione con la sessione remota, utilizzando l' -topzione; per esempio:

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

    • Si noti che il readprompt interattivo funziona correttamente solo con un pty, quindi l' -topzione è necessaria.

    • L'uso di un pty ha un notevole effetto collaterale: stdout e stderr sono combinati ed entrambi riportati tramite stdout ; in altre parole: si perde la distinzione tra output regolare ed errore; per esempio:

      • 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

In assenza di questo argomento, sshcrea una shell interattiva , incluso quando si inviano comandi tramite stdin , che è dove inizia il problema:

  • Per una shell interattiva , sshnormalmente alloca un pty (pseudo-terminale) per impostazione predefinita, tranne se il suo stdin non è collegato a un terminale (reale).

    • L'invio di comandi tramite stdin significa che lo sshstdin non è più collegato a un terminale, quindi non viene creato nessun pty e ti ssh avverte di conseguenza :
      Pseudo-terminal will not be allocated because stdin is not a terminal.

    • Anche l' -topzione, il cui scopo esplicito è richiedere la creazione di un pty, non è sufficiente in questo caso : riceverai lo stesso avviso.

      • Un po 'curiosamente, devi quindi raddoppiare l' -topzione per forzare la creazione di un pty: ssh -t -t ...o ssh -tt ...dimostra che lo intendi davvero, davvero .

      • Forse la logica per richiedere questo passo molto deliberato è che le cose potrebbero non funzionare come previsto . Ad esempio, su macOS 10.12, l'equivalente apparente del comando sopra, fornendo i comandi tramite stdin e usando -tt, non funziona correttamente; la sessione si blocca dopo aver risposto al readprompt:
        ssh -tt jdoe@server <<<'read -p "Enter something: "; echo "Entered: [$REPLY]"'


Nel caso improbabile che i comandi che desideri passare come argomento rendano la riga di comando troppo lunga per il tuo sistema (se la sua lunghezza si avvicina getconf ARG_MAX- leggi questo articolo ), considera di copiare prima il codice nel sistema remoto sotto forma di uno script ( utilizzando, ad esempio,scp ), quindi inviare un comando per eseguire quello script.

In un pizzico, usa -Te fornisci i comandi tramite stdin , con un exitcomando finale , ma nota che se hai anche bisogno di funzionalità interattive, usare -ttal posto di -Tpotrebbe non funzionare.


1
Funzionava perfettamente, il modo -tt stava facendo visualizzare al terminale ogni comando inviato tramite ssh.
Segna E il

1
"Raddoppia l'opzione -t per forzare la creazione di un pty: ssh -t -t ... o ssh -tt ... mostra che lo intendi davvero, davvero." - ah ah ah - grazie divertente ma anche serio - Non riesco a mettere la testa intorno al doppio t tutto quello che so è che se inserisco un singolo t non si muove
DavidC

22

Non so da dove provenga l'hang, ma il reindirizzamento (o il piping) dei comandi in un ssh interattivo è in genere una ricetta per i problemi. È più robusto usare lo stile command-to-run-as-a-last-topic e passare lo script sulla riga comandi 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'

(Tutto in un gigante ' argomento della riga di comando multilinea delimitato).

Il messaggio pseudo-terminale è dovuto al tuo, -tche chiede a ssh di provare a far apparire l'ambiente che gira sul computer remoto come un vero terminale per i programmi che vi girano. Il vostro client ssh si rifiuta di farlo perché il suo proprio standard input non è un terminale, in modo che non ha modo di passare le API di terminali speciali in poi dalla macchina remota al terminale effettivo alla fine locale.

Che cosa stavi cercando di ottenere -tcomunque?


1
L'opzione -t è stata un tentativo di risolvere il problema del terminale Psuedo (non ha funzionato). Ho provato la tua soluzione, si è sbarazzato della cosa del terminale psuedo ma ora si blocca e basta ...
Matteo,

4
@Henning: Puoi per favore elaborare o fornire un link per quanto riguarda gli svantaggi del reindirizzamento o del piping in un ssh interattivo?
Isaac Kleinman,

un po 'in ritardo ma: qui non hai bisogno di uno pseudo terminale, quindi usa l'opzione -Tinvece.
Florian Castellane,

6

Dopo aver letto molte di queste risposte, ho pensato di condividere la mia soluzione risultante. Tutto ciò che ho aggiunto è/bin/bash prima dell'eredità e non dà più l'errore.

Usa questo:

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

Invece di questo (dà errore):

ssh user@machine <<'ENDSSH'
   hostname
ENDSSH

Oppure usa questo:

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

Invece di questo (dà errore):

ssh user@machine < run-command.sh

EXTRA :

Se si desidera comunque un prompt interattivo remoto, ad esempio se lo script in esecuzione in remoto richiede una password o altre informazioni, poiché le soluzioni precedenti non consentono di digitare i prompt.

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

E se vuoi anche registrare l'intera sessione in un file logfile.log:

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

0

Stavo riscontrando lo stesso errore in Windows usando emacs 24.5.1 per connettermi ad alcuni server aziendali tramite / ssh: user @ host. Ciò che ha risolto il mio problema è stato impostare la variabile "tramp-default-method" su "plink" e ogni volta che mi collego a un server ommetto il protocollo ssh. Devi aver installato il plink.exe di PuTTY per farlo funzionare.

Soluzione

  1. Mx personalizza-variabile (e quindi premi Invio)
  2. tramp-default-method (e quindi premi nuovamente Invio)
  3. Nel campo di testo inserisci plink e quindi Applica e salva il buffer
  4. Ogni volta che provo ad accedere a un server remoto ora uso Cxf / user @ host: e quindi inserisco la password. La connessione ora viene stabilita correttamente sotto Emacs su Windows al mio server remoto.

-3

ssh -t foobar @ localhost yourscript.pl


2
Anche l'OP usa -t, ma nel loro scenario specifico non è abbastanza, il che ha spinto la domanda a cominciare.
mklement0,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.