TL; DR
Non usare -t
. -t
comporta uno pseudo-terminale sull'host remoto e deve essere utilizzato solo per eseguire applicazioni visive da un terminale.
Spiegazione
Il carattere di avanzamento riga (noto anche come newline o \n
) è quello che, quando inviato a un terminale, dice al terminale di spostare il cursore verso il basso.
Tuttavia, quando corri seq 3
in un terminale, è lì che seq
scrive 1\n2\n3\n
qualcosa del genere /dev/pts/0
, non vedi:
1
2
3
ma
1
2
3
Perché?
In realtà, quando seq 3
(o ssh host seq 3
per quella materia) scrive 1\n2\n3\n
, il terminale vede 1\r\n2\r\n3\r\n
. In altre parole, gli avanzamenti di riga sono stati tradotti in ritorno a capo (su cui i terminali spostano il cursore indietro a sinistra dello schermo) e avanzamento di riga.
Questo viene fatto dal driver del dispositivo terminale. Più precisamente, dalla disciplina di linea del dispositivo terminale (o pseudo-terminale), un modulo software che risiede nel kernel.
Puoi controllare il comportamento di quella disciplina di riga con il stty
comando. La traduzione di LF
-> CRLF
è attivata con
stty onlcr
(che è generalmente abilitato per impostazione predefinita). Puoi disattivarlo con:
stty -onlcr
Oppure puoi disattivare tutta l'elaborazione dell'output con:
stty -opost
Se lo fai e corri seq 3
, vedrai:
$ stty -onlcr; seq 3
1
2
3
come previsto.
Ora, quando lo fai:
seq 3 > some-file
seq
non sta più scrivendo su un terminale, sta scrivendo in un file, non è in corso alcuna traduzione. Quindi some-file
contiene 1\n2\n3\n
. La traduzione viene eseguita solo quando si scrive su un dispositivo terminale. Ed è fatto solo per la visualizzazione.
allo stesso modo, quando lo fai:
ssh host seq 3
ssh
sta scrivendo 1\n2\n3\n
indipendentemente ssh
dall'output.
Ciò che effettivamente accade è che il seq 3
comando viene eseguito host
con il suo stdout reindirizzato a una pipe. Il ssh
server sull'host legge l'altra estremità del pipe e lo invia al ssh
client tramite il canale crittografato e il ssh
client lo scrive sul suo stdout, nel tuo caso un dispositivo pseudo-terminale, dove LF
vengono tradotti CRLF
per la visualizzazione.
Molte applicazioni interattive si comportano diversamente quando il loro stdout non è un terminale. Ad esempio, se si esegue:
ssh host vi
vi
non gli piace, non gli piace che il suo output vada in pipe. Pensa che non stia parlando con un dispositivo in grado di comprendere, ad esempio, le sequenze di escape del posizionamento del cursore.
Quindi ssh
ha l' -t
opzione per quello. Con tale opzione, il server ssh sull'host crea un dispositivo pseudo-terminale e lo rende stdout (e stdin e stderr) di vi
. Ciò che vi
scrive su quel dispositivo terminale passa attraverso quella disciplina di linea pseudo-terminale remota e viene letto dal ssh
server e inviato al ssh
client tramite il canale crittografato . È lo stesso di prima tranne che invece di usare una pipe , il ssh
server usa uno pseudo-terminale .
L'altra differenza è che sul lato client, il ssh
client imposta il terminale in raw
modalità. Ciò significa che non viene eseguita alcuna traduzione ( opost
è disabilitato e anche altri comportamenti sul lato input). Ad esempio, quando si digita Ctrl-C, invece di interrompere ssh
, quel ^C
carattere viene inviato al lato remoto, dove la disciplina di linea dello pseudo-terminale remoto invia l' interrupt al comando remoto.
Quando lo fai:
ssh -t host seq 3
seq 3
scrive 1\n2\n3\n
sul suo stdout, che è un dispositivo pseudo-terminale. A causa di onlcr
, che viene tradotto su un host per 1\r\n2\r\n3\r\n
e inviato sul canale criptato. Dalla tua parte non c'è traduzione ( onlcr
disabilitato), quindi 1\r\n2\r\n3\r\n
viene visualizzato intatto (a causa della raw
modalità) e correttamente sullo schermo dell'emulatore di terminale.
Ora, se lo fai:
ssh -t host seq 3 > some-file
Non c'è alcuna differenza dall'alto. ssh
scriverò la stessa cosa:, 1\r\n2\r\n3\r\n
ma questa volta in some-file
.
Quindi praticamente tutto l' LF
output di seq
è stato tradotto CRLF
in some-file
.
È lo stesso se lo fai:
ssh -t host cat remote-file > local-file
Tutti i LF
caratteri (0x0a byte) vengono tradotti in CRLF (0x0d 0x0a).
Questo è probabilmente il motivo della corruzione nel tuo file. Nel caso del secondo file più piccolo, accade che il file non contenga byte 0x0a, quindi non c'è corruzione.
Si noti che è possibile ottenere diversi tipi di corruzione con impostazioni tty diverse. Un altro potenziale tipo di corruzione associato -t
è se i tuoi file di avvio su host
( ~/.bashrc
, ~/.ssh/rc
...) scrivono cose sul loro stderr, perché con -t
lo stdout e lo stderr della shell remota finiscono per essere uniti nello ssh
stdout (entrambi vanno allo pseudo dispositivo terminale).
Non si desidera che il telecomando venga cat
emesso su un dispositivo terminale lì.
Tu vuoi:
ssh host cat remote-file > local-file
Potresti fare:
ssh -t host 'stty -opost; cat remote-file` > local-file
Funzionerebbe (tranne che nella stesura del caso di corruzione stderr discusso in precedenza), ma anche quello sarebbe subottimale poiché si avrebbe lo strato pseudo-terminale non necessario in esecuzione host
.
Ancora più divertente:
$ ssh localhost echo | od -tx1
0000000 0a
0000001
OK.
$ ssh -t localhost echo | od -tx1
0000000 0d 0a
0000002
LF
tradotto in CRLF
$ ssh -t localhost 'stty -opost; echo' | od -tx1
0000000 0a
0000001
OK di nuovo
$ ssh -t localhost 'stty olcuc; echo x'
X
Questa è un'altra forma di post-elaborazione dell'output che può essere eseguita dalla disciplina della linea terminale.
$ echo x | ssh -t localhost 'stty -opost; echo' | od -tx1
Pseudo-terminal will not be allocated because stdin is not a terminal.
stty: standard input: Inappropriate ioctl for device
0000000 0a
0000001
ssh
si rifiuta di dire al server di utilizzare uno pseudo-terminale quando il proprio input non è un terminale. Puoi forzarlo con -tt
però:
$ echo x | ssh -tt localhost 'stty -opost; echo' | od -tx1
0000000 x \r \n \n
0000004
La disciplina di linea fa molto di più sul lato input.
Qui, echo
non legge il suo input né è stato chiesto di emetterlo, x\r\n\n
quindi da dove viene? Questo è il locale echo
dello pseudo-terminale remoto ( stty echo
). Il ssh
server sta alimentando la x\n
lettura dal client al lato master dello pseudo-terminale remoto. E la disciplina di linea di ciò riecheggia (prima che stty opost
venga eseguito, ecco perché vediamo un CRLF
e non LF
). Questo è indipendente dal fatto che l'applicazione remota legga o meno qualcosa da stdin.
$ (sleep 1; printf '\03') | ssh -tt localhost 'trap "echo ouch" INT; sleep 2'
^Couch
Il 0x3
personaggio viene ripetuto come ^C
( ^
e C
) a causa di stty echoctl
e shell e sleep ricevono un SIGINT perché stty isig
.
Allora mentre:
ssh -t host cat remote-file > local-file
è abbastanza male, ma
ssh -tt host 'cat > remote-file' < local-file
trasferire i file dall'altra parte è molto peggio. Otterrete alcuni CR -> Traduzione LF, ma anche problemi con tutti i caratteri speciali ( ^C
, ^Z
, ^D
, ^?
, ^S
...) e anche il telecomando cat
non vedrà EOF quando la fine del local-file
raggiungimento, solo quando ^D
viene inviato dopo una \r
, \n
o un altro ^D
come quando fai cat > file
nel tuo terminale.
-t
opzione che interrompe il trasferimento. Non utilizzare-t
o-T
, a meno che non sia necessario per un motivo molto specifico. L'impostazione predefinita funziona nella stragrande maggioranza dei casi, quindi queste opzioni sono molto raramente necessarie.