TL; DR
Đừng sử dụng -t
. -t
liê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 3
trong một thiết bị đầu cuối, đó là nơi seq
ghi 1\n2\n3\n
và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 3
cho 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 stty
lệ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
seq
khô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-file
có 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\n
bất kể ssh
đầu ra của cái gì .
Điều thực sự xảy ra là seq 3
lệnh được chạy host
với thiết bị xuất chuẩn của nó được chuyển hướng đến một đường ống. Máy ssh
chủ trên máy chủ đọc đầu kia của ống và gửi nó qua kênh được mã hóa đến ssh
máy khách của bạn và ssh
má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 đó LF
s được dịch để CRLF
hiể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
vi
khô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
có -t
tù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ì vi
ghi 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 ssh
máy chủ đọc và gửi qua kênh được mã hóa đến ssh
máy khách. Nó giống như trước đây ngoại trừ việc thay vì sử dụng đường ống , ssh
má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, ssh
máy khách đặt thiết bị đầu cuối ở raw
chế độ. Điều đó có nghĩa là không có bản dịch nào được thực hiện ở đó ( opost
bị 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
, ^C
ký 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 3
ghi 1\n2\n3\n
và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\n
và gửi đến bạn qua kênh được mã hóa. Về phía bạn, không có bản dịch ( onlcr
bị vô hiệu hóa), do đó 1\r\n2\r\n3\r\n
được hiển thị không bị ảnh hưởng (vì raw
chế độ) 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. ssh
sẽ 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 CRLF
sang 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 LF
ký 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 -t
là 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 -t
thiế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 ssh
thiế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 cat
xuấ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
ssh
từ 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 -tt
mặ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, echo
không đọc đầu vào của nó và cũng không được yêu cầu đầu ra x\r\n\n
như vậy nó đến từ đâu? Đó là địa phương echo
của thiết bị đầu cuối giả ( stty echo
). Máy ssh
chủ đang cho x\n
nó đọ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 opost
là chạy, đó là lý do tại sao chúng ta thấy một CRLF
và 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ý 0x3
tự được lặp lại là ^C
( ^
và C
) vì stty echoctl
và 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 cat
sẽ 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
, \n
hoặc khác ^D
như khi làm cat > file
trong thiết bị đầu cuối của bạn.
-t
tùy chọn, phá vỡ sự chuyển giao. Đừng sử dụng-t
hoặ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.