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, sshcó -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
Ký 0x3tự được lặp lại là ^C( ^và 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.
-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.