TL; DR
Ne pas utiliser -t. -timplique un pseudo-terminal sur l'hôte distant et ne doit être utilisé que pour exécuter des applications visuelles à partir d'un terminal.
Explication
Le caractère de saut de ligne (également appelé retour à la ligne ou \n) est celui qui, lorsqu'il est envoyé à un terminal, indique au terminal de déplacer son curseur vers le bas.
Pourtant, lorsque vous exécutez seq 3dans un terminal, c'est là seqqu'écrit 1\n2\n3\nquelque chose comme /dev/pts/0, vous ne voyez pas:
1
2
3
mais
1
2
3
Pourquoi donc?
En fait, quand seq 3(ou ssh host seq 3d'ailleurs) écrit 1\n2\n3\n, le terminal voit 1\r\n2\r\n3\r\n. Autrement dit, les sauts de ligne ont été traduits en retour chariot (sur lequel les terminaux déplacent leur curseur vers la gauche de l'écran) et en saut de ligne.
Cela se fait par le pilote de périphérique terminal. Plus exactement, par la discipline de ligne du terminal (ou pseudo-terminal), un module logiciel qui réside dans le noyau.
Vous pouvez contrôler le comportement de cette discipline de ligne avec la sttycommande. La traduction de LF-> CRLFest activée avec
stty onlcr
(qui est généralement activé par défaut). Vous pouvez le désactiver avec:
stty -onlcr
Ou vous pouvez désactiver tous les traitements de sortie avec:
stty -opost
Si vous faites cela et exécutez seq 3, vous verrez alors:
$ stty -onlcr; seq 3
1
2
3
comme prévu.
Maintenant, quand vous le faites:
seq 3 > some-file
seqn'écrit plus sur un terminal, il écrit dans un fichier, aucune traduction n'est en cours. Contient some-filedonc 1\n2\n3\n. La traduction n'est effectuée que lors de l'écriture sur un terminal. Et cela n'est fait que pour l'affichage.
de même, lorsque vous faites:
ssh host seq 3
sshécrit 1\n2\n3\nquelle que soit sshla sortie.
Ce qui se passe réellement, c'est que la seq 3commande est exécutée hostavec sa sortie standard redirigée vers un canal. Le sshserveur sur l'hôte lit l'autre extrémité du canal et l'envoie sur le canal crypté à votre sshclient et le sshclient l'écrit sur sa sortie standard, dans votre cas un périphérique pseudo-terminal, où LFs sont traduits en CRLFpour être affichés.
De nombreuses applications interactives se comportent différemment lorsque leur sortie standard n'est pas un terminal. Par exemple, si vous exécutez:
ssh host vi
vine l'aime pas, il n'aime pas que sa sortie passe dans un tuyau. Il pense qu'il ne parle pas à un appareil capable de comprendre les séquences d'échappement de positionnement du curseur par exemple.
A donc sshla -tpossibilité pour cela. Avec cette option, le serveur ssh sur l'hôte crée un périphérique pseudo-terminal et en fait la stdout (et stdin et stderr) de vi. Ce qui viécrit sur ce terminal passe par cette discipline de ligne de pseudo-terminal distant et est lu par le sshserveur et envoyé sur le canal crypté au sshclient. C'est la même chose qu'avant, sauf qu'au lieu d'utiliser un canal , le sshserveur utilise un pseudo-terminal .
L'autre différence est que du côté client, le sshclient met le terminal en rawmode. Cela signifie qu'aucune traduction n'y est effectuée ( opostest désactivée et également d'autres comportements côté entrée). Par exemple, lorsque vous tapez Ctrl-C, au lieu d'interrompre ssh, ce ^Ccaractère est envoyé vers le côté distant, où la discipline de ligne du pseudo-terminal distant envoie l' interruption à la commande distante.
Quand vous faites:
ssh -t host seq 3
seq 3écrit 1\n2\n3\nsur sa sortie standard, qui est un périphérique pseudo-terminal. En raison de onlcr, qui obtient traduit sur l' hôte pour 1\r\n2\r\n3\r\net vous sera envoyé sur le canal crypté. De votre côté, il n'y a pas de traduction ( onlcrdésactivée), 1\r\n2\r\n3\r\nest donc affiché intact (en raison du rawmode) et correctement sur l'écran de votre émulateur de terminal.
Maintenant, si vous le faites:
ssh -t host seq 3 > some-file
Il n'y a aucune différence d'en haut. sshva écrire la même chose:, 1\r\n2\r\n3\r\nmais cette fois en some-file.
Donc, fondamentalement, toutes les LFsorties de seqont été traduites CRLFen some-file.
C'est la même chose si vous le faites:
ssh -t host cat remote-file > local-file
Tous les LFcaractères (0x0a octets) sont en cours de traduction en CRLF (0x0d 0x0a).
C'est probablement la raison de la corruption de votre fichier. Dans le cas du deuxième fichier plus petit, il se trouve que le fichier ne contient pas d'octets 0x0a, il n'y a donc pas de corruption.
Notez que vous pouvez obtenir différents types de corruption avec différents paramètres tty. Un autre type potentiel de corruption associé à -test si vos fichiers de démarrage sur host( ~/.bashrc, ~/.ssh/rc...) écrivent des choses sur leur stderr, car avec -tle stdout et le stderr du shell distant finissent par être fusionnés dans sshle stdout de (ils vont tous les deux au pseudo - dispositif terminal).
Vous ne voulez pas que la télécommande catémette sur un périphérique terminal.
Tu veux:
ssh host cat remote-file > local-file
Vous pourriez faire:
ssh -t host 'stty -opost; cat remote-file` > local-file
Cela fonctionnerait (sauf dans l' écriture dans le cas de corruption stderr discuté ci-dessus), mais même cela serait sous-optimal car vous auriez cette couche pseudo-terminale inutile en cours d'exécution host.
Un peu plus de plaisir:
$ ssh localhost echo | od -tx1
0000000 0a
0000001
D'ACCORD.
$ ssh -t localhost echo | od -tx1
0000000 0d 0a
0000002
LF traduit en CRLF
$ ssh -t localhost 'stty -opost; echo' | od -tx1
0000000 0a
0000001
OK encore.
$ ssh -t localhost 'stty olcuc; echo x'
X
C'est une autre forme de post-traitement de sortie qui peut être effectuée par la discipline de la ligne 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
sshrefuse de dire au serveur d'utiliser un pseudo-terminal lorsque sa propre entrée n'est pas un terminal. Vous pouvez cependant le forcer avec -tt:
$ echo x | ssh -tt localhost 'stty -opost; echo' | od -tx1
0000000 x \r \n \n
0000004
La discipline de ligne en fait beaucoup plus du côté des entrées.
Ici, echone lit pas son entrée et n'a pas été invité à le sortir, x\r\n\nalors d'où vient-il? C'est le local echodu pseudo-terminal distant ( stty echo). Le sshserveur alimente la x\nlecture du client vers le côté maître du pseudo-terminal distant. Et la discipline de ligne qui en fait écho (avant stty opostest exécutée, c'est pourquoi nous voyons un CRLFet non LF). Cela est indépendant du fait que l'application distante lit ou non quoi que ce soit à partir de stdin.
$ (sleep 1; printf '\03') | ssh -tt localhost 'trap "echo ouch" INT; sleep 2'
^Couch
Le 0x3caractère est renvoyé en écho comme ^C( ^et C) à cause de stty echoctlet le shell et sleep reçoivent un SIGINT car stty isig.
Donc pendant:
ssh -t host cat remote-file > local-file
est déjà assez mauvais, mais
ssh -tt host 'cat > remote-file' < local-file
transférer des fichiers dans l'autre sens est bien pire. Vous obtenez des CR -> traduction de LF, mais aussi des problèmes avec tous les caractères spéciaux ( ^C, ^Z, ^D, ^?, ^S...) ainsi que la télécommande catne verra pas EOF lorsque la fin local-fileest atteint, seulement quand ^Dest envoyé après \r, \nou un autre ^Dcomme lorsque vous faites cat > filedans votre terminal.
-toption qui rompt le transfert. N'utilisez pas-tou-T, sauf si vous en avez besoin pour une raison très précise. La valeur par défaut fonctionne dans la grande majorité des cas, donc ces options sont très rarement nécessaires.