Tại sao tập tin nhị phân này được chuyển qua trên ssh -tvà bị thay đổi?


29

Tôi đang cố gắng sao chép các tập tin qua SSH , nhưng không thể sử dụng scpdo không biết tên tệp chính xác mà tôi cần. Mặc dù các tệp nhị phân nhỏ và các tệp văn bản chuyển tốt, các tệp nhị phân lớn bị thay đổi. Đây là tập tin trên máy chủ:

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

Đây là tập tin sau khi tôi chuyển nó qua:

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

Lưu ý rằng tệp đã tải xuống lớn hơn tệp trên máy chủ! Tuy nhiên, nếu tôi làm tương tự với một tệp rất nhỏ, thì mọi thứ sẽ hoạt động như mong đợi:

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

người dùng thực 0m3.041s 0m0.013s sys 0m0.005s

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

Cả hai kích thước tệp là 26 byte trong trường hợp này.

Tại sao các tệp nhỏ có thể chuyển tốt, nhưng các tệp lớn có thêm một số byte vào chúng?


10
Đó là -ttùy chọn, phá vỡ sự chuyển giao. Đừng sử dụng -thoặc -T, trừ khi bạn cần chúng vì một lý do rất cụ thể. Mặc định hoạt động trong phần lớn các trường hợp, vì vậy những tùy chọn đó rất hiếm khi cần thiết.
kasperd

3
Không bao giờ nghĩ rằng tôi sẽ nói điều này trong thế kỷ này, nhưng bạn có thể muốn thử uuencode và uudecode nếu đó ssh -t catlà cách duy nhất để truyền tệp.
Đánh dấu Plotnick

1
@MarkPlotnick phiên bản hiện đại của uuencode / uudecode hiện được đặt tên là base64 / base64 -d
Archemar

Câu trả lời:


60

TL; DR

Đừng sử dụng -t. -tliên quan đến một thiết bị đầu cuối giả trên máy chủ từ xa và chỉ nên được sử dụng để chạy các ứng dụng trực quan từ thiết bị đầu cuối.

Giải trình

Ký tự nguồn cấp dữ liệu (còn được gọi là dòng mới hoặc \n) là ký tự mà khi được gửi đến thiết bị đầu cuối sẽ báo cho thiết bị đầu cuối di chuyển con trỏ xuống.

Tuy nhiên, khi bạn chạy seq 3trong một thiết bị đầu cuối, đó là nơi seqghi 1\n2\n3\nvào một cái gì đó như /dev/pts/0, bạn không thấy:

1
 2
  3

nhưng

1
2
3

Tại sao vậy?

Trên thực tế, khi seq 3(hoặc ssh host seq 3cho vấn đề đó) viết 1\n2\n3\n, thiết bị đầu cuối nhìn thấy 1\r\n2\r\n3\r\n. Đó là, các nguồn cấp dữ liệu đã được dịch thành quay trở lại vận chuyển (trên đó các thiết bị đầu cuối di chuyển con trỏ trở lại bên trái màn hình) và nguồn cấp dữ liệu.

Điều đó được thực hiện bởi trình điều khiển thiết bị đầu cuối. Chính xác hơn, theo kỷ luật dòng của thiết bị đầu cuối (hoặc giả thiết bị đầu cuối), một mô-đun phần mềm nằm trong kernel.

Bạn có thể kiểm soát hành vi của kỷ luật dòng đó bằng sttylệnh. Bản dịch của LF-> CRLFđược bật với

stty onlcr

(thường được bật theo mặc định). Bạn có thể tắt nó bằng:

stty -onlcr

Hoặc bạn có thể tắt tất cả xử lý đầu ra với:

stty -opost

Nếu bạn làm điều đó và chạy seq 3, bạn sẽ thấy:

$ stty -onlcr; seq 3
1
 2
  3

như mong đợi.

Bây giờ, khi bạn làm:

seq 3 > some-file

seqkhông còn ghi vào thiết bị đầu cuối, nó ghi vào một tệp, không có bản dịch nào được thực hiện. Vì vậy, some-filecó chứa 1\n2\n3\n. Việc dịch chỉ được thực hiện khi ghi vào thiết bị đầu cuối. Và nó chỉ được thực hiện để hiển thị.

tương tự, khi bạn làm:

ssh host seq 3

sshđang viết 1\n2\n3\nbất kể sshđầu ra của cái gì .

Điều thực sự xảy ra là seq 3lệnh được chạy hostvới thiết bị xuất chuẩn của nó được chuyển hướng đến một đường ống. Máy sshchủ trên máy chủ đọc đầu kia của ống và gửi nó qua kênh được mã hóa đến sshmáy khách của bạn và sshmáy khách ghi nó vào thiết bị xuất chuẩn của nó, trong trường hợp của bạn là thiết bị đầu cuối giả, trong đó LFs được dịch để CRLFhiển thị.

Nhiều ứng dụng tương tác hoạt động khác nhau khi thiết bị xuất chuẩn của chúng không phải là thiết bị đầu cuối. Chẳng hạn, nếu bạn chạy:

ssh host vi

vikhông thích nó, nó không giống như đầu ra của nó đi vào một đường ống. Nó nghĩ rằng nó không nói chuyện với một thiết bị có thể hiểu các chuỗi thoát định vị con trỏ chẳng hạn.

Vì vậy, ssh-ttùy chọn cho điều đó. Với tùy chọn đó, máy chủ ssh trên máy chủ tạo ra một thiết bị đầu cuối giả và làm cho thiết bị xuất chuẩn (và stdin và stderr) của vi. Những gì vighi trên thiết bị đầu cuối đó đi qua kỷ luật dòng thiết bị đầu cuối giả từ xa đó và được sshmáy chủ đọc và gửi qua kênh được mã hóa đến sshmáy khách. Nó giống như trước đây ngoại trừ việc thay vì sử dụng đường ống , sshmáy chủ sử dụng thiết bị đầu cuối giả .

Sự khác biệt khác là ở phía máy khách, sshmáy khách đặt thiết bị đầu cuối ở rawchế độ. Điều đó có nghĩa là không có bản dịch nào được thực hiện ở đó ( opostbị vô hiệu hóa và cả các hành vi phía đầu vào khác). Chẳng hạn, khi bạn gõ Ctrl-C, thay vì ngắt ssh, ^Cký tự đó được gửi đến phía từ xa, trong đó kỷ luật dòng của thiết bị đầu cuối giả từ xa sẽ gửi ngắt đến lệnh từ xa.

Khi bạn làm:

ssh -t host seq 3

seq 3ghi 1\n2\n3\nvào thiết bị xuất chuẩn của nó, một thiết bị đầu cuối giả. Bởi vì onlcr, đó được dịch trên máy chủ để 1\r\n2\r\n3\r\nvà gửi đến bạn qua kênh được mã hóa. Về phía bạn, không có bản dịch ( onlcrbị vô hiệu hóa), do đó 1\r\n2\r\n3\r\nđược hiển thị không bị ảnh hưởng (vì rawchế độ) và chính xác trên màn hình của trình giả lập thiết bị đầu cuối của bạn.

Bây giờ, nếu bạn làm:

ssh -t host seq 3 > some-file

Không có sự khác biệt từ trên. sshsẽ viết điều tương tự : 1\r\n2\r\n3\r\n, nhưng lần này vào some-file.

Vì vậy, về cơ bản tất cả các LFđầu ra của seqđã được dịch CRLFsang some-file.

Nó giống nhau nếu bạn làm:

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

Tất cả các LFký tự (0x0a byte) đang được dịch sang CRLF (0x0d 0x0a).

Đó có lẽ là lý do cho sự tham nhũng trong tập tin của bạn. Trong trường hợp tệp nhỏ thứ hai, thực tế là tệp không chứa byte 0x0a, do đó không có tham nhũng.

Lưu ý rằng bạn có thể nhận được các loại tham nhũng khác nhau với các cài đặt tty khác nhau. Một loại tham nhũng tiềm năng khác có liên quan -tlà nếu các tệp khởi động của bạn trên host( ~/.bashrc, ~/.ssh/rc...) viết mọi thứ vào -tthiết bị xuất chuẩn của chúng, bởi vì với thiết bị xuất chuẩn và thiết bị xuất chuẩn của vỏ từ xa cuối cùng sẽ được hợp nhất thành sshthiết bị xuất chuẩn (cả hai đều đi đến giả thiết bị -terminal).

Bạn không muốn điều khiển từ xa catxuất ra thiết bị đầu cuối ở đó.

Bạn muốn:

ssh host cat remote-file > local-file

Bạn có thể làm:

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

Điều đó sẽ hoạt động (ngoại trừ bằng văn bản cho trường hợp tham nhũng stderr đã thảo luận ở trên), nhưng thậm chí điều đó sẽ không tối ưu vì bạn có lớp thiết bị đầu cuối giả không cần thiết chạy trên đó host.


Một số niềm vui khác:

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

ĐƯỢC.

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

LF dịch sang CRLF

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

Được rồi lại lần nữa.

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

Đó là một hình thức xử lý hậu kỳ đầu ra khác có thể được thực hiện bằng kỷ luật dòng thiết bị đầu cuối.

$ 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

sshtừ chối yêu cầu máy chủ sử dụng thiết bị đầu cuối giả khi đầu vào của chính nó không phải là thiết bị đầu cuối. Bạn có thể buộc nó bằng -ttmặc dù:

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

Các kỷ luật dòng làm nhiều hơn nữa về phía đầu vào.

Ở đây, echokhông đọc đầu vào của nó và cũng không được yêu cầu đầu ra x\r\n\nnhư vậy nó đến từ đâu? Đó là địa phương echocủa thiết bị đầu cuối giả ( stty echo). Máy sshchủ đang cho x\nnó đọc từ máy khách đến phía chủ của thiết bị đầu cuối giả từ xa. Và kỷ luật dòng của nó lặp lại (trước đó stty opostlà chạy, đó là lý do tại sao chúng ta thấy một CRLFvà không LF). Điều đó độc lập với việc ứng dụng từ xa có đọc bất cứ thứ gì từ stdin hay không.

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

0x3tự được lặp lại là ^C( ^C) vì stty echoctlvà vỏ và giấc ngủ nhận được SIGINT bởi vì stty isig.

Vì vậy, trong khi:

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

là đủ xấu, nhưng

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

chuyển tập tin theo cách khác là tồi tệ hơn nhiều. Bạn sẽ nhận được một số CR -> LF dịch, mà còn vấn đề với tất cả các ký tự đặc biệt ( ^C, ^Z, ^D, ^?, ^S...) và cũng có thể điều khiển từ xa catsẽ không thấy eof khi kết thúc local-fileđược đạt tới, chỉ khi ^Dđược gửi đi sau khi một \r, \nhoặc khác ^Dnhư khi làm cat > filetrong thiết bị đầu cuối của bạn.


5

Khi sử dụng phương pháp đó để sao chép tệp, các tệp có vẻ khác nhau.

Máy chủ từ xa

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

Máy chủ cục bộ

Chạy ssh ... catlệnh của bạn :

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

Kết quả trong tệp này trên máy chủ cục bộ:

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

Điều tra tại sao?

Điều tra tệp kết quả ở phía địa phương cho thấy rằng nó đã bị hỏng. Nếu bạn thực hiện -tchuyển đổi ra khỏi sshlệnh của bạn thì nó hoạt động như mong đợi.

$ 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

Tổng kiểm tra bây giờ cũng hoạt động:

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

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

Cảm ơn Sim. Mặc dù trên thực tế bạn là người đầu tiên đăng câu trả lời đúng, tôi đã chọn Stéphane cho câu trả lời được chọn do độ sâu của lời giải thích của anh ấy. Đừng lo lắng, bạn đã có một lịch sử bài viết dài mà tôi đang học hỏi, và tất nhiên tôi nâng cao những bài đăng mà tôi học được. Cảm ơn bạn.
dotancohen

@dotancohen - không phải lo lắng, bạn chấp nhận điều mà bạn cảm thấy là người giúp bạn trở thành OP nhiều nhất 8-). Khả năng của anh ta để giải thích tại sao mọi thứ xảy ra là vô song, ngoại trừ bởi Gilles.
slm
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.