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
клиент записывает его на своем стандартный вывод, в вашем случае псевдо-терминал, где LF
s переводится на 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, потому что с -t
stdout и stderr удаленной оболочки в конечном итоге объединяются в ssh
stdout (они оба переходят к псевдо -терминальное устройство).
Вы не хотите, чтобы пульт дистанционного управления 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
, если они вам не нужны по очень конкретной причине. По умолчанию работает в подавляющем большинстве случаев, так что эти опции очень редко нужны.