TL; DR
Ne pas utiliser -t
. -t
implique 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 3
dans un terminal, c'est là seq
qu'écrit 1\n2\n3\n
quelque 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 3
d'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 stty
commande. La traduction de LF
-> CRLF
est 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
seq
n'écrit plus sur un terminal, il écrit dans un fichier, aucune traduction n'est en cours. Contient some-file
donc 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\n
quelle que soit ssh
la sortie.
Ce qui se passe réellement, c'est que la seq 3
commande est exécutée host
avec sa sortie standard redirigée vers un canal. Le ssh
serveur sur l'hôte lit l'autre extrémité du canal et l'envoie sur le canal crypté à votre ssh
client et le ssh
client l'écrit sur sa sortie standard, dans votre cas un périphérique pseudo-terminal, où LF
s sont traduits en CRLF
pour ê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
vi
ne 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 ssh
la -t
possibilité 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 ssh
serveur et envoyé sur le canal crypté au ssh
client. C'est la même chose qu'avant, sauf qu'au lieu d'utiliser un canal , le ssh
serveur utilise un pseudo-terminal .
L'autre différence est que du côté client, le ssh
client met le terminal en raw
mode. Cela signifie qu'aucune traduction n'y est effectuée ( opost
est 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 ^C
caractè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\n
sur 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\n
et vous sera envoyé sur le canal crypté. De votre côté, il n'y a pas de traduction ( onlcr
désactivée), 1\r\n2\r\n3\r\n
est donc affiché intact (en raison du raw
mode) 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. ssh
va écrire la même chose:, 1\r\n2\r\n3\r\n
mais cette fois en some-file
.
Donc, fondamentalement, toutes les LF
sorties de seq
ont été traduites CRLF
en some-file
.
C'est la même chose si vous le faites:
ssh -t host cat remote-file > local-file
Tous les LF
caractè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é à -t
est si vos fichiers de démarrage sur host
( ~/.bashrc
, ~/.ssh/rc
...) écrivent des choses sur leur stderr, car avec -t
le stdout et le stderr du shell distant finissent par être fusionnés dans ssh
le 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
ssh
refuse 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, echo
ne lit pas son entrée et n'a pas été invité à le sortir, x\r\n\n
alors d'où vient-il? C'est le local echo
du pseudo-terminal distant ( stty echo
). Le ssh
serveur alimente la x\n
lecture du client vers le côté maître du pseudo-terminal distant. Et la discipline de ligne qui en fait écho (avant stty opost
est exécutée, c'est pourquoi nous voyons un CRLF
et 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 0x3
caractère est renvoyé en écho comme ^C
( ^
et C
) à cause de stty echoctl
et 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 cat
ne verra pas EOF lorsque la fin local-file
est atteint, seulement quand ^D
est envoyé après \r
, \n
ou un autre ^D
comme lorsque vous faites cat > file
dans votre terminal.
-t
option qui rompt le transfert. N'utilisez pas-t
ou-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.