為什麼這個通過“ssh -t”傳輸的二進製文件被改變了?
我正在嘗試通過 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
客戶端將其寫入其標準輸出,在您的情況下是偽終端設備,其中LF
s 被轉換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
都被翻譯CRLF
成some-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
不會看到 eoflocal-file
,只有^D
在 a 之後發送時\r
,\n
或在您的終端中^D
進行的其他類似操作。cat > file