Ssh

為什麼這個通過“ssh -t”傳輸的二進製文件被改變了?

  • February 4, 2022

我正在嘗試通過 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 系統 0m0.005s

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

在這種情況下,兩個文件大小都是 26 字節。

為什麼小文件可以正常傳輸,但大文件會添加一些字節?

TL; 博士

不要使用-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其標準輸出重定向到管道的情況下執行。ssh主機上的伺服器讀取管道的另一端並通過加密通道將其發送到您的ssh客戶端,然後ssh客戶端將其寫入其標準輸出,在您的情況下是偽終端設備,其中LFs 被轉換CRLF為顯示。

當標準輸出不是終端時,許多互動式應用程序的行為會有所不同。例如,如果您執行:

ssh host vi

vi不喜歡它,它不喜歡它的輸出進入管道。例如,它認為它不是在與能夠理解游標定位轉義序列的設備交談。

所以ssh有這個-t選項。使用該選項,主機上的 ssh 伺服器會創建一個偽終端設備,並使其成為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都被翻譯CRLFsome-file.

如果你這樣做是一樣的:

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

所有LF字元(0x0a 字節)都被翻譯成 CRLF(0x0d 0x0a)。

這可能是文件損壞的原因。在第二個較小文件的情況下,恰好該文件不包含 0x0a 字節,因此沒有損壞。

請注意,使用不同的 tty 設置可能會導致不同類型的損壞。與之相關的另一種潛在的損壞類型-t是,如果您在host( ~/.bashrc, ~/.ssh/rc…) 上的啟動文件將內容寫入它們的標準錯誤,因為-t遠端 shell 的標準輸出和標準錯誤最終被合併到ssh標準輸出中(它們都進入偽-終端設備)。

您不希望遙控器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執行之前,這就是我們看到 aCRLF和 not的原因LF)。這與遠端應用程序是否從標準輸入讀取任何內容無關。

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

0x3字元被回顯為^C(^C) 因為stty echoctl並且 shell 和 sleep 收到一個 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…)的問題,並且當到達 end 時遙控器cat不會看到 eof local-file,只有^D在 a 之後發送時\r\n或在您的終端中^D進行的其他類似操作。cat > file

引用自:https://unix.stackexchange.com/questions/151916