TL; DR
-tอย่าใช้ -tเกี่ยวข้องกับเทอร์มินัลหลอกบนรีโมตโฮสต์และควรใช้เพื่อรันแอปพลิเคชันแบบเห็นภาพจากเทอร์มินัลเท่านั้น
คำอธิบาย
อักขระ linefeed (หรือที่รู้จักในชื่อ newline หรือ\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, terminal 1\r\n2\r\n3\r\nเห็น นั่นคือฟีดบรรทัดได้รับการแปลเป็น carriage-return (ซึ่งเทอร์มินัลย้ายเคอร์เซอร์ของพวกเขากลับไปทางซ้ายของหน้าจอ) และฟีดบรรทัด
ที่ทำโดยไดรเวอร์อุปกรณ์ปลายทาง ยิ่งไปกว่านั้นโดยบรรทัดการลงวินัยของอุปกรณ์เทอร์มินัล (หรือสถานีหลอก) โมดูลซอฟต์แวร์ที่อยู่ในเคอร์เนล
คุณสามารถควบคุมพฤติกรรมของวินัยในบรรทัดนั้นด้วย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โดย stdout เปลี่ยนเส้นทางไปยังไพพ์ sshเซิร์ฟเวอร์บนโฮสต์อ่านส่วนอื่น ๆ ของท่อและส่งมันมากกว่าช่องทางเข้ารหัสเพื่อคุณsshลูกค้าและsshลูกค้าเขียนมันลงบน stdout ของตนในกรณีของคุณอุปกรณ์หลอกขั้วที่LFs แปลงCRLFสำหรับการแสดงผล
แอ็พพลิเคชันแบบโต้ตอบจำนวนมากทำงานแตกต่างกันเมื่อ stdout ไม่ใช่เทอร์มินัล ตัวอย่างเช่นหากคุณเรียกใช้:
ssh host vi
viไม่ชอบมันไม่ชอบเอาท์พุทมันจะไปป์ มันคิดว่ามันไม่ได้พูดคุยกับอุปกรณ์ที่สามารถทำความเข้าใจกับลำดับการหลีกตำแหน่งเคอร์เซอร์เช่น
ดังนั้นsshมี-tตัวเลือกสำหรับสิ่งนั้น ด้วยตัวเลือกที่เซิร์ฟเวอร์ SSH บนโฮสต์สร้างอุปกรณ์หลอกขั้วและทำให้ว่า stdout (และ stdin และ stderr) viของ สิ่งที่viเขียนบนอุปกรณ์เทอร์มินัลนั้นต้องผ่านระเบียบวินัยการใช้สายปลอมหลอกระยะไกลและถูกอ่านโดยsshเซิร์ฟเวอร์และส่งผ่านช่องสัญญาณที่เข้ารหัสไปยังsshลูกค้า มันเป็นเช่นเดียวกับก่อนยกเว้นว่าแทนการใช้ท่อที่sshเซิร์ฟเวอร์ใช้ขั้วหลอก
ข้อแตกต่างอื่น ๆ คือที่ฝั่งsshไคลเอ็นต์ไคลเอ็นต์ตั้งค่าเทอร์มินัลในrawโหมด ซึ่งหมายความว่าจะไม่มีการแปลที่นั่น ( opostถูกปิดใช้งานและพฤติกรรมด้านอินพุตอื่น ๆ ) ตัวอย่างเช่นเมื่อคุณพิมพ์Ctrl-Cแทนการขัดจังหวะการsshที่^Cตัวละครถูกส่งไปยังด้านระยะไกลที่มีระเบียบวินัยสายระยะไกลหลอกขั้วส่งขัดจังหวะกับคำสั่งจากระยะไกล
เมื่อคุณทำ:
ssh -t host seq 3
seq 3เขียน1\n2\n3\nลงใน stdout ซึ่งเป็นอุปกรณ์แบบหลอกเทียม เพราะ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\nsome-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... ) เขียนสิ่งต่าง ๆ ลงใน stderr เนื่องจาก-tstdout และ stderr ของเชลล์ระยะไกลจบลงด้วยการรวมเข้าเป็นsshstdout ของพวกเขา - อุปกรณ์ปลายทาง)
คุณไม่ต้องการให้รีโมต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ถูกเรียกใช้ซึ่งเป็นเหตุผลที่เราเห็นCRLFและไม่ได้LF) ไม่ขึ้นอยู่กับว่าแอปพลิเคชันระยะไกลอ่านอะไรจาก stdin หรือไม่
$ (sleep 1; printf '\03') | ssh -tt localhost 'trap "echo ouch" INT; sleep 2'
^Couch
0x3ตัวละครที่สะท้อนกลับมาเป็น^C( ^และC) เพราะstty echoctlและเปลือกและการนอนหลับได้รับ 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... ) และยังห่างไกลcatจะไม่เห็น EOF เมื่อสิ้นสุดของการlocal-fileถึงเฉพาะเมื่อ^Dถูกส่งหลังจาก\r, \nหรือคนอื่น^Dชอบเมื่อทำcat > fileใน terminal ของคุณ
-tตัวเลือกซึ่งแบ่งการถ่ายโอน อย่าใช้-tหรือ-Tยกเว้นว่าคุณต้องการเหตุผลพิเศษ ค่าเริ่มต้นใช้งานได้ในกรณีส่วนใหญ่ดังนั้นตัวเลือกเหล่านี้จึงไม่ค่อยจำเป็น