TL; DR
Не используйте -t. -tвключает в себя псевдотерминал на удаленном хосте и должен использоваться только для запуска визуальных приложений из терминала.
объяснение
Символ перевода строки (также называемый символом новой строки или \n) - это тот, который при отправке на терминал указывает терминалу переместить курсор вниз.
Тем не менее, когда вы запускаете seq 3в терминале, именно здесь seqпишет 1\n2\n3\nчто-то вроде /dev/pts/0, вы не видите:
1
2
3
но
1
2
3
Почему это?
На самом деле, когда seq 3(или ssh host seq 3в этом отношении) пишет 1\n2\n3\n, терминал видит 1\r\n2\r\n3\r\n. То есть переводы строки были переведены на возврат каретки (после чего терминалы перемещают курсор назад влево от экрана) и перевод строки.
Это делается драйвером оконечного устройства. Точнее говоря, с помощью линейной дисциплины терминального (или псевдотерминального) устройства - программный модуль, который находится в ядре.
Вы можете контролировать поведение этой линии дисциплины с помощью sttyкоманды. Перевод LF-> CRLFвключен с
stty onlcr
(который обычно включен по умолчанию). Вы можете отключить его с помощью:
stty -onlcr
Или вы можете отключить всю обработку вывода с помощью:
stty -opost
Если вы сделаете это и запустите seq 3, вы увидите:
$ stty -onlcr; seq 3
1
2
3
как и ожидалось.
Теперь, когда вы делаете:
seq 3 > some-file
seqбольше не пишет в терминал, он пишет в файл, перевод не выполняется. Так some-fileи содержится 1\n2\n3\n. Перевод выполняется только при записи на терминальное устройство. И это только для отображения.
аналогично, когда вы делаете:
ssh host seq 3
sshпишет 1\n2\n3\nнезависимо от того, на что sshидет вывод.
На самом деле происходит то, что seq 3команда запускается hostсо своим stdout, перенаправленным в канал. sshСервер на хосте читает другой конец трубы и отправить его через зашифрованный канал для вашего sshклиента , и sshклиент записывает его на своем стандартный вывод, в вашем случае псевдо-терминал, где LFs переводится на CRLFдля отображения.
Многие интерактивные приложения ведут себя по-разному, когда их стандартный вывод не является терминалом. Например, если вы запустите:
ssh host vi
viему не нравится, ему не нравится, когда его вывод идет в трубу. Он думает, что не разговаривает с устройством, которое может, например, понимать escape-последовательности позиционирования курсора.
Так что sshесть -tвозможность для этого. С этой опцией сервер ssh на хосте создает псевдо-терминальное устройство и делает его stdout (и stdin, и stderr) из vi. То, что viпишет на этом оконечном устройстве, проходит через эту дисциплину линии удаленного псевдотерминала, читается sshсервером и отправляется по зашифрованному каналу sshклиенту. Это то же самое , как и раньше , за исключением , что вместо того , чтобы использовать трубу , то sshсервер использует псевдо-терминал .
Другое отличие состоит в том, что на стороне sshклиента клиент устанавливает терминал в rawрежим. Это означает, что там не выполняется перевод ( opostотключен, а также другие действия на стороне ввода). Например, при вводе Ctrl-Cвместо прерывания sshэтот ^Cсимвол отправляется на удаленную сторону, где дисциплина линии удаленного псевдо-терминала отправляет прерывание на удаленную команду.
Когда вы делаете:
ssh -t host seq 3
seq 3пишет 1\n2\n3\nв свой стандартный вывод, который является псевдо-терминальным устройством. Из - за onlcr, что переводится на хозяина к 1\r\n2\r\n3\r\nи отправлен вам по зашифрованному каналу. На вашей стороне нет перевода ( onlcrотключен), поэтому 1\r\n2\r\n3\r\nотображается нетронутым (из-за rawрежима) и правильно на экране вашего эмулятора терминала.
Теперь, если вы делаете:
ssh -t host seq 3 > some-file
Там нет никакой разницы сверху. sshнапишу тоже самое 1\r\n2\r\n3\r\n, но на этот раз в some-file.
Таким образом, в основном все LFв результате seqбыли переведены CRLFв some-file.
То же самое, если вы делаете:
ssh -t host cat remote-file > local-file
Все LFсимволы (0x0a байтов) переводятся в CRLF (0x0d 0x0a).
Это, вероятно, причина коррупции в вашем файле. В случае второго меньшего файла, так получилось, что файл не содержит байтов 0x0a, поэтому искажения отсутствуют.
Обратите внимание, что вы можете получить различные типы повреждений с разными настройками tty. Другой потенциальный тип повреждения, связанный с тем -t, что ваши загрузочные файлы host( ~/.bashrc, ~/.ssh/rc...) записывают вещи в их stderr, потому что с -tstdout и stderr удаленной оболочки в конечном итоге объединяются в sshstdout (они оба переходят к псевдо -терминальное устройство).
Вы не хотите, чтобы пульт дистанционного управления catвыводил на терминальное устройство.
Вы хотите:
ssh host cat remote-file > local-file
Вы могли бы сделать:
ssh -t host 'stty -opost; cat remote-file` > local-file
Это будет работать (за исключением случая записи в stderr, рассмотренного выше), но даже это будет неоптимальным, так как у вас будет работать этот ненужный псевдотерминальный уровень host.
Еще немного веселья:
$ ssh localhost echo | od -tx1
0000000 0a
0000001
ХОРОШО.
$ ssh -t localhost echo | od -tx1
0000000 0d 0a
0000002
LF переведено на CRLF
$ ssh -t localhost 'stty -opost; echo' | od -tx1
0000000 0a
0000001
Хорошо снова
$ ssh -t localhost 'stty olcuc; echo x'
X
Это еще одна форма пост-обработки вывода, которая может быть выполнена дисциплиной терминальной линии.
$ 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отказывается указывать серверу использовать псевдо-терминал, когда его собственный ввод не является терминалом. Вы можете заставить это, -ttхотя:
$ echo x | ssh -tt localhost 'stty -opost; echo' | od -tx1
0000000 x \r \n \n
0000004
Дисциплина линии делает намного больше на стороне ввода.
Здесь, echoне читает его ввод и не было предложено вывести это, x\r\n\nтак откуда это взялось? Это локальный echoпсевдотерминал ( stty echo). sshСервер кормления x\nона считывается из клиента к главной стороне удаленного псевдо-терминала. И дисциплина линии этого повторяет это (прежде, чем stty opostбежать, именно поэтому мы видим, CRLFа не LF). Это не зависит от того, читает ли удаленное приложение что-либо из стандартного ввода или нет.
$ (sleep 1; printf '\03') | ssh -tt localhost 'trap "echo ouch" INT; sleep 2'
^Couch
0x3Символ эхо , как ^C( ^а C) из - за , stty echoctlа оболочка и сон получают SIGINT , потому что stty isig.
Так что пока:
ssh -t host cat remote-file > local-file
достаточно плохо, но
ssh -tt host 'cat > remote-file' < local-file
передавать файлы в другую сторону намного хуже. Вы получите некоторые CR -> LF перевода, но и проблемы со всеми специальными символами ( ^C, ^Z, ^D, ^?, ^S...) , а также пульт catне будет видеть ВФ , когда конец local-fileдостигается только тогда , когда ^Dотправляется после \r, \nили другой, ^Dкак при выполнении cat > fileв вашем терминале.
-tвариант, который нарушает передачу. Не используйте-tили-T, если они вам не нужны по очень конкретной причине. По умолчанию работает в подавляющем большинстве случаев, так что эти опции очень редко нужны.