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
для відображення.
Багато інтерактивних додатків поводяться по-різному, коли їх stdout не є терміналом. Наприклад, якщо ви запускаєте:
ssh host vi
vi
це не подобається, йому не подобається, що його вихід іде на трубу. Він думає, що це не розмова з пристроєм, який здатний зрозуміти, наприклад, позиції втечі позиціонування курсору.
Так 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
в свій stdout, який є псевдотермінальним пристроєм. З - за 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 байт, тому немає пошкодження.
Зауважте, що ви можете отримати різні типи корупції з різними налаштуваннями. Ще один потенційний тип корупції, пов’язаний із -t
тим, що якщо ваші файли запуску на host
( ~/.bashrc
, ~/.ssh/rc
...) записують речі в їх stderr, тому що, -t
коли stdout і stderr віддаленої оболонки в кінцевому підсумку об'єднуються в ssh
stdout s (вони обидва переходять до псевдо -термінальний пристрій).
Ви не хочете, щоб пульт cat
виводив на термінальний пристрій там.
Ти хочеш:
ssh host cat remote-file > local-file
Ви можете зробити:
ssh -t host 'stty -opost; cat remote-file` > local-file
Це може спрацювати (за винятком написання вище обговореної вище корупційної справи), але навіть це було б неоптимальним, оскільки у вас буде працювати цей непотрібний псевдотермінальний шар 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
). Це незалежно від того, чи читає віддалений додаток щось із stdin чи ні.
$ (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
, якщо вони вам не знадобляться з дуже конкретної причини. За замовчуванням діє переважна більшість випадків, тому такі варіанти дуже рідкісні.