TL; DR
Nie używać -t
. -t
obejmuje 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 3
w terminalu, czyli tam, gdzie seq
pisze 1\n2\n3\n
coś takiego /dev/pts/0
, nie widzisz:
1
2
3
ale
1
2
3
Dlaczego?
W rzeczywistości, kiedy seq 3
(lub ssh host seq 3
w 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
-> CRLF
jest 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
seq
nie zapisuje już na terminalu, zapisuje do pliku, tłumaczenie nie jest wykonywane. Więc some-file
zawiera 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
ssh
pisze 1\n2\n3\n
niezależnie od tego, do czego ssh
zmierza wyjście.
W rzeczywistości dzieje się tak, że seq 3
polecenie jest uruchamiane host
z przekierowaniem standardu na potok. ssh
Serwera hosta odczytuje na drugi koniec rury i wysłać go na zaszyfrowany kanał do ssh
klienta, a ssh
klient zapisuje go na jego standardowe wyjście, w przypadku urządzenia pseudo-terminali, gdzie LF
s są tłumaczone na CRLF
na 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
vi
nie 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ż ssh
jest -t
opcja. 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 vi
pisze na tym urządzeniu końcowym, przechodzi przez tę dyscyplinę zdalnej linii pseudoterminalowej i jest odczytywane przez ssh
serwer i wysyłane zaszyfrowanym kanałem do ssh
klienta. To samo, jak poprzednio z tym, że zamiast stosowania rury The ssh
serwera wykorzystuje pseudoterminal .
Inna różnica polega na tym, że po stronie ssh
klienta klient ustawia terminal w raw
trybie. Oznacza to, że nie wykonuje się tam tłumaczenia ( opost
jest wyłączone, a także inne zachowania po stronie wejściowej). Na przykład, gdy piszesz Ctrl-C, zamiast przerywania ssh
, ^C
znak 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 3
zapisuje 1\n2\n3\n
na 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\n
i wysłane przez szyfrowany kanał. Po twojej stronie nie ma tłumaczenia ( onlcr
wyłączone), więc 1\r\n2\r\n3\r\n
jest wyświetlane nietknięte (z powodu raw
trybu) 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. ssh
napisze to samo: 1\r\n2\r\n3\r\n
ale tym razem w some-file
.
Więc w zasadzie wszystkie dane LF
wyjściowe seq
zostały przetłumaczone CRLF
na some-file
.
To samo, jeśli wykonasz:
ssh -t host cat remote-file > local-file
Wszystkie LF
znaki (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 -t
jest to, że pliki startowe na host
( ~/.bashrc
, ~/.ssh/rc
...) zapisują rzeczy do swojego stderr, ponieważ wraz -t
ze stdout i stderr zdalnej powłoki są scalane w ssh
stdout (oba idą do pseudo - urządzenie końcowe).
Nie chcesz, aby pilot wysyłał cat
sygnał 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
ssh
odmawia poinformowania serwera, aby używał pseudo-terminala, gdy jego własne dane wejściowe nie są terminalem. Możesz to -tt
jednak 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 echo
nie czyta danych wejściowych ani nie został poproszony o przesłanie tego, x\r\n\n
więc skąd to pochodzi? To jest lokalny echo
zdalny pseudo-terminal ( stty echo
). ssh
Serwer jest karmienie x\n
go czytać od klienta do strony głównej zdalnego pseudo-terminala. A dyscyplina liniowa tego powtarza to z powrotem (przed stty opost
biegiem, dlatego widzimy CRLF
a 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
0x3
Postać jest jak echo ^C
( ^
a C
) z powodu stty echoctl
a 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 cat
nie będzie widoczne EOF, gdy koniec local-file
zostanie osiągnięty tylko wtedy, gdy ^D
jest wysyłany po \r
, \n
lub inny, ^D
jak robisz cat > file
w swoim terminalu.
-t
opcja, która przerywa transfer. Nie używaj-t
lub-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.