TL; DR
Non usare -t. -tcomporta 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 3in un terminale, è lì che seqscrive 1\n2\n3\nqualcosa del genere /dev/pts/0, non vedi:
1
2
3
ma
1
2
3
Perché?
In realtà, quando seq 3(o ssh host seq 3per 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 sttycomando. 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
seqnon sta più scrivendo su un terminale, sta scrivendo in un file, non è in corso alcuna traduzione. Quindi some-filecontiene 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
sshsta scrivendo 1\n2\n3\nindipendentemente sshdall'output.
Ciò che effettivamente accade è che il seq 3comando viene eseguito hostcon il suo stdout reindirizzato a una pipe. Il sshserver sull'host legge l'altra estremità del pipe e lo invia al sshclient tramite il canale crittografato e il sshclient lo scrive sul suo stdout, nel tuo caso un dispositivo pseudo-terminale, dove LFvengono tradotti CRLFper la visualizzazione.
Molte applicazioni interattive si comportano diversamente quando il loro stdout non è un terminale. Ad esempio, se si esegue:
ssh host vi
vinon 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 sshha l' -topzione 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 viscrive su quel dispositivo terminale passa attraverso quella disciplina di linea pseudo-terminale remota e viene letto dal sshserver e inviato al sshclient tramite il canale crittografato . È lo stesso di prima tranne che invece di usare una pipe , il sshserver usa uno pseudo-terminale .
L'altra differenza è che sul lato client, il sshclient imposta il terminale in rawmodalità. 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 ^Ccarattere 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 3scrive 1\n2\n3\nsul suo stdout, che è un dispositivo pseudo-terminale. A causa di onlcr, che viene tradotto su un host per 1\r\n2\r\n3\r\ne inviato sul canale criptato. Dalla tua parte non c'è traduzione ( onlcrdisabilitato), quindi 1\r\n2\r\n3\r\nviene visualizzato intatto (a causa della rawmodalità) 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. sshscriverò la stessa cosa:, 1\r\n2\r\n3\r\nma questa volta in some-file.
Quindi praticamente tutto l' LFoutput di seqè stato tradotto CRLFin some-file.
È lo stesso se lo fai:
ssh -t host cat remote-file > local-file
Tutti i LFcaratteri (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 -tlo stdout e lo stderr della shell remota finiscono per essere uniti nello sshstdout (entrambi vanno allo pseudo dispositivo terminale).
Non si desidera che il telecomando venga catemesso 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
sshsi rifiuta di dire al server di utilizzare uno pseudo-terminale quando il proprio input non è un terminale. Puoi forzarlo con -ttperò:
$ 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, echonon legge il suo input né è stato chiesto di emetterlo, x\r\n\nquindi da dove viene? Questo è il locale echodello pseudo-terminale remoto ( stty echo). Il sshserver sta alimentando la x\nlettura dal client al lato master dello pseudo-terminale remoto. E la disciplina di linea di ciò riecheggia (prima che stty opostvenga eseguito, ecco perché vediamo un CRLFe 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 0x3personaggio viene ripetuto come ^C( ^e C) a causa di stty echoctle 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 catnon vedrà EOF quando la fine del local-fileraggiungimento, solo quando ^Dviene inviato dopo una \r, \no un altro ^Dcome quando fai cat > filenel tuo terminale.
-topzione che interrompe il trasferimento. Non utilizzare-to-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.