Dlaczego ten plik binarny jest przesyłany przez „ssh -t”?


29

Próbuję skopiować pliki przez SSH , ale nie mogę ich użyć, scpponieważ nie znam dokładnej nazwy pliku, której potrzebuję. Chociaż małe pliki binarne i pliki tekstowe przesyłają się dobrze, duże pliki binarne ulegają zmianie. Oto plik na serwerze:

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

Oto plik po przeniesieniu go:

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

Pamiętaj, że pobrany plik jest większy niż ten na serwerze! Jeśli jednak zrobię to samo z bardzo małym plikiem, wszystko działa zgodnie z oczekiwaniami:

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

rzeczywisty 0m3.041s użytkownik 0m0.013s sys 0m0.005s

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

Oba rozmiary plików mają w tym przypadku 26 bajtów.

Dlaczego małe pliki mogą być przesyłane dobrze, ale do dużych plików dodawane są niektóre bajty?


10
Jest to -topcja, która przerywa transfer. Nie używaj -tlub -T, chyba że potrzebujesz ich z bardzo konkretnego powodu. Domyślnie działa w zdecydowanej większości przypadków, więc te opcje są bardzo rzadko potrzebne.
kasperd

3
Nigdy nie myślałem, że powiem to w tym stuleciu, ale możesz spróbować uuencode i uudecode, jeśli ssh -t catjest to jedyny sposób przesyłania plików.
Mark Plotnick,

1
@MarkPlotnick nowoczesna wersja kodu uuencode / uudecode nosi teraz nazwę base64 / base64 -d
Archemar

Odpowiedzi:


60

TL; DR

Nie używać -t. -tobejmuje pseudo-terminal na zdalnym hoście i powinien być używany tylko do uruchamiania aplikacji wizualnych z terminala.

Wyjaśnienie

Znak przesunięcia linii (znany również jako znak nowej linii lub \n) to ten, który po wysłaniu do terminala informuje terminal o przesunięciu kursora w dół.

Jednak podczas uruchamiania seq 3w terminalu, czyli tam, gdzie seqpisze 1\n2\n3\ncoś takiego /dev/pts/0, nie widzisz:

1
 2
  3

ale

1
2
3

Dlaczego?

W rzeczywistości, kiedy seq 3(lub ssh host seq 3w tym przypadku) pisze 1\n2\n3\n, terminal widzi 1\r\n2\r\n3\r\n. Oznacza to, że przesunięcia linii zostały przetłumaczone na powrót karetki (po której terminale przesuwają kursor z powrotem na lewą stronę ekranu) i przesunięcie linii.

Odbywa się to przez sterownik urządzenia końcowego. Dokładniej, według dyscypliny liniowej urządzenia terminalowego (lub pseudo-terminalnego), moduł oprogramowania, który znajduje się w jądrze.

Za pomocą polecenia można kontrolować zachowanie tej dyscypliny liniowej stty. Tłumaczenie LF-> CRLFjest włączone za pomocą

stty onlcr

(który zazwyczaj jest domyślnie włączony). Możesz to wyłączyć za pomocą:

stty -onlcr

Lub możesz wyłączyć wszystkie przetwarzanie danych wyjściowych za pomocą:

stty -opost

Jeśli to zrobisz i uruchomisz seq 3, zobaczysz:

$ stty -onlcr; seq 3
1
 2
  3

zgodnie z oczekiwaniami.

Teraz, kiedy to zrobisz:

seq 3 > some-file

seqnie zapisuje już na terminalu, zapisuje do pliku, tłumaczenie nie jest wykonywane. Więc some-filezawiera 1\n2\n3\n. Tłumaczenie jest wykonywane tylko podczas pisania na urządzeniu końcowym. I to tylko na pokaz.

podobnie, gdy robisz:

ssh host seq 3

sshpisze 1\n2\n3\nniezależnie od tego, do czego sshzmierza wyjście.

W rzeczywistości dzieje się tak, że seq 3polecenie jest uruchamiane hostz przekierowaniem standardu na potok. sshSerwera hosta odczytuje na drugi koniec rury i wysłać go na zaszyfrowany kanał do sshklienta, a sshklient zapisuje go na jego standardowe wyjście, w przypadku urządzenia pseudo-terminali, gdzie LFs są tłumaczone na CRLFna wyświetlaczu.

Wiele interaktywnych aplikacji zachowuje się inaczej, gdy ich standardowe wyjście nie jest terminalem. Na przykład, jeśli uruchomisz:

ssh host vi

vinie lubi tego, nie lubi, aby jego wyjście trafiało do potoku. Uważa, że ​​nie rozmawia z urządzeniem, które może na przykład zrozumieć sekwencje specjalne pozycjonowania kursora.

Tak też sshjest -topcja. Dzięki tej opcji serwer ssh na hoście tworzy pseudoterminalowe urządzenie i sprawia, że ​​stdout (i stdin i stderr) z vi. To, co vipisze na tym urządzeniu końcowym, przechodzi przez tę dyscyplinę zdalnej linii pseudoterminalowej i jest odczytywane przez sshserwer i wysyłane zaszyfrowanym kanałem do sshklienta. To samo, jak poprzednio z tym, że zamiast stosowania rury The sshserwera wykorzystuje pseudoterminal .

Inna różnica polega na tym, że po stronie sshklienta klient ustawia terminal w rawtrybie. Oznacza to, że nie wykonuje się tam tłumaczenia ( opostjest wyłączone, a także inne zachowania po stronie wejściowej). Na przykład, gdy piszesz Ctrl-C, zamiast przerywania ssh, ^Cznak ten jest wysyłany do strony zdalnej, gdzie dyscyplina liniowa zdalnego pseudo-terminala wysyła przerwanie do polecenia zdalnego.

Kiedy to zrobisz:

ssh -t host seq 3

seq 3zapisuje 1\n2\n3\nna standardowe wyjście, które jest pseudoterminalnym urządzeniem. Ze względu onlcr, że zostanie przetłumaczony na hosta do 1\r\n2\r\n3\r\ni wysłane przez szyfrowany kanał. Po twojej stronie nie ma tłumaczenia ( onlcrwyłączone), więc 1\r\n2\r\n3\r\njest wyświetlane nietknięte (z powodu rawtrybu) i poprawnie na ekranie emulatora terminala.

Teraz, jeśli to zrobisz:

ssh -t host seq 3 > some-file

Nie ma różnicy z góry. sshnapisze to samo: 1\r\n2\r\n3\r\nale tym razem w some-file.

Więc w zasadzie wszystkie dane LFwyjściowe seqzostały przetłumaczone CRLFna some-file.

To samo, jeśli wykonasz:

ssh -t host cat remote-file > local-file

Wszystkie LFznaki (0x0a bajtów) są tłumaczone na CRLF (0x0d 0x0a).

Prawdopodobnie jest to przyczyną uszkodzenia pliku. W przypadku drugiego mniejszego pliku dzieje się tak, że plik nie zawiera bajtów 0x0a, więc nie ma uszkodzenia.

Pamiętaj, że możesz uzyskać różne rodzaje uszkodzenia przy różnych ustawieniach tty. Innym potencjalnym rodzajem uszkodzenia związanego z tym -tjest to, że pliki startowe na host( ~/.bashrc, ~/.ssh/rc...) zapisują rzeczy do swojego stderr, ponieważ wraz -tze stdout i stderr zdalnej powłoki są scalane w sshstdout (oba idą do pseudo - urządzenie końcowe).

Nie chcesz, aby pilot wysyłał catsygnał do urządzenia końcowego.

Chcesz:

ssh host cat remote-file > local-file

Mógłbyś:

ssh -t host 'stty -opost; cat remote-file` > local-file

To by działało (oprócz omówionego powyżej przypadku zapisu do uszkodzenia stderr ), ale nawet to byłoby nieoptymalne, ponieważ działałaby niepotrzebna warstwa pseudoterminalna host.


Więcej zabawy:

$ ssh localhost echo | od -tx1
0000000 0a
0000001

DOBRZE.

$ ssh -t localhost echo | od -tx1
0000000 0d 0a
0000002

LF przetłumaczone na CRLF

$ ssh -t localhost 'stty -opost; echo' | od -tx1
0000000 0a
0000001

OK ponownie

$ ssh -t localhost 'stty olcuc; echo x'
X

Jest to kolejna forma przetwarzania końcowego, która może być wykonana przez dyscyplinę na linii terminalowej.

$ 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

sshodmawia poinformowania serwera, aby używał pseudo-terminala, gdy jego własne dane wejściowe nie są terminalem. Możesz to -ttjednak wymusić :

$ echo x | ssh -tt localhost 'stty -opost; echo' | od -tx1
0000000   x  \r  \n  \n
0000004

Dyscyplina liniowa robi znacznie więcej po stronie wejściowej.

Tutaj echonie czyta danych wejściowych ani nie został poproszony o przesłanie tego, x\r\n\nwięc skąd to pochodzi? To jest lokalny echozdalny pseudo-terminal ( stty echo). sshSerwer jest karmienie x\ngo czytać od klienta do strony głównej zdalnego pseudo-terminala. A dyscyplina liniowa tego powtarza to z powrotem (przed stty opostbiegiem, dlatego widzimy CRLFa nie LF). Jest to niezależne od tego, czy zdalna aplikacja odczytuje coś ze standardowego wejścia, czy nie.

$ (sleep 1; printf '\03') | ssh -tt localhost 'trap "echo ouch" INT; sleep 2'
^Couch

0x3Postać jest jak echo ^C( ^a C) z powodu stty echoctla powłoka i spać otrzyma SIGINT bo stty isig.

Więc gdy:

ssh -t host cat remote-file > local-file

jest wystarczająco zły, ale

ssh -tt host 'cat > remote-file' < local-file

przesyłanie plików w drugą stronę jest znacznie gorsze. Dostaniesz kilka CR -> Tłumaczenia LF, ale również problemy z wszystkich znaków specjalnych ( ^C, ^Z, ^D, ^?, ^S...), a także pilot catnie będzie widoczne EOF, gdy koniec local-filezostanie osiągnięty tylko wtedy, gdy ^Djest wysyłany po \r, \nlub inny, ^Djak robisz cat > filew swoim terminalu.


5

Gdy używasz tej metody do skopiowania pliku, pliki wydają się być inne.

Zdalny serwer

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

Serwer lokalny

Uruchamianie ssh ... catpolecenia:

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

Wyniki w tym pliku na serwerze lokalnym:

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

Badanie dlaczego?

Badanie wynikowego pliku po stronie lokalnej pokazuje, że został uszkodzony. Jeśli usuniesz -tprzełącznik z sshpolecenia, będzie działał zgodnie z oczekiwaniami.

$ 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

Sumy kontrolne teraz też działają:

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

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

Dziękuję Sim. Chociaż w rzeczywistości byłeś pierwszym, który opublikował poprawną odpowiedź, wybrałem Stéphane dla wybranej odpowiedzi ze względu na głębokość jego wyjaśnienia. Nie martw się, masz długą historię postów, z której się uczę, i oczywiście głosuję za tymi postami, z których się uczę. Dziękuję Ci.
dotancohen

@dotancohen - nie martw się, akceptujesz to, co kiedykolwiek czujesz, że A są tymi, które pomagają ci jako OP 8-). Jego zdolność do wyjaśniania, dlaczego coś się dzieje, jest bezkonkurencyjna, z wyjątkiem Gillesa.
slm
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.