Чому цей бінарний файл, переданий через "ssh -t", змінюється?


29

Я намагаюся скопіювати файли через SSH , але не можу використовувати scpчерез те, що не знаю точного імені файлу, яке мені потрібно. Хоча малі бінарні файли та текстові файли добре передаються, великі бінарні файли змінюються. Ось файл на сервері:

remote$ ls -la
-rw-rw-r--  1 user user 244970907 Aug 24 11:11 foo.gz
remote$ md5sum foo.gz 
9b5a44dad9d129bab52cbc6d806e7fda foo.gz

Ось файл після перенесення його:

local$ time ssh me@server.com -t 'cat /path/to/foo.gz' > latest.gz

real    1m52.098s
user    0m2.608s
sys     0m4.370s
local$ md5sum latest.gz
76fae9d6a4711bad1560092b539d034b  latest.gz

local$ ls -la
-rw-rw-r--  1 dotancohen dotancohen 245849912 Aug 24 18:26 latest.gz

Зауважте, що завантажений файл більше, ніж файл на сервері! Однак якщо я те ж саме роблю з дуже маленьким файлом, то все працює так, як очікувалося:

remote$ echo "Hello" | gzip -c > hello.txt.gz
remote$ md5sum hello.txt.gz
08bf5080733d46a47d339520176b9211  hello.txt.gz

local$ time ssh me@server.com -t 'cat /path/to/hello.txt.gz' > hi.txt.gz

реальний користувач 0m3.041s 0m0.013s sys 0m0.005s

local$ md5sum hi.txt.gz
08bf5080733d46a47d339520176b9211  hi.txt.gz

Обидва розміри файлів у цьому випадку становлять 26 байт.

Чому маленькі файли можуть передавати штрафи, але великі файли додають до них байт?


10
Це -tваріант, який порушує передачу. Не використовуйте -tабо -T, якщо вони вам не знадобляться з дуже конкретної причини. За замовчуванням діє переважна більшість випадків, тому такі варіанти дуже рідкісні.
kasperd

3
Ніколи не думав, що я скажу це у цьому столітті, але ви можете спробувати uuencode та uudecode, якщо ssh -t catце єдиний спосіб передачі файлів.
Марк Плотнік

1
@MarkPlotnick сучасна версія uuencode / uudecode тепер називається base64 / base64 -d
Archemar

Відповіді:


60

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для відображення.

Багато інтерактивних додатків поводяться по-різному, коли їх 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 віддаленої оболонки в кінцевому підсумку об'єднуються в sshstdout 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у вашому терміналі.


5

При використанні цього методу для копіювання файлу файли, здається, відрізняються.

Віддалений сервер

ls -l | grep vim_cfg
-rw-rw-r--.  1 slm slm 9783257 Aug  5 16:51 vim_cfg.tgz

Локальний сервер

Запуск ssh ... catкоманди:

$ ssh dufresne -t 'cat ~/vim_cfg.tgz' > vim_cfg.tgz

Результати в цьому файлі на локальному сервері:

$ ls -l | grep vim_cfg.tgz 
-rw-rw-r--. 1 saml saml 9820481 Aug 24 12:13 vim_cfg.tgz

Розслідування чому?

Дослідження отриманого файлу на локальній стороні показує, що він пошкоджений. Якщо ви виймете -tперемикач зі своєї sshкоманди, то він працює як очікувалося.

$ ssh dufresne 'cat ~/vim_cfg.tgz' > vim_cfg.tgz

$ ls -l | grep vim_cfg.tgz
-rw-rw-r--. 1 saml saml 9783257 Aug 24 12:17 vim_cfg.tgz

Тепер контрольні суми теж працюють:

# remote server
$ ssh dufresne "md5sum ~/vim_cfg.tgz"
9e70b036836dfdf2871e76b3636a72c6  /home/slm/vim_cfg.tgz

# local server
$ md5sum vim_cfg.tgz 
9e70b036836dfdf2871e76b3636a72c6  vim_cfg.tgz

Дякую, Сім. Хоча насправді ви першими опублікували правильну відповідь, я вибрав Стефана для обраної відповіді через глибину його пояснення. Не хвилюйтесь, у вас є довга історія публікацій, з якої я навчаюсь, і, звичайно, я підтримую ті посади, з яких я вчуся. Дякую.
dotancohen

@dotancohen - не хвилюйся, ти приймаєш те, що коли-небудь, що ти відчуваєш, є тими, хто допомагає тобі як ОП найбільше 8-). Його здібності пояснювати, чому все відбувається, неперевершені, за винятком Гілла.
slm
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.