เหตุใดไฟล์ไบนารีนี้จึงถูกถ่ายโอนผ่าน“ ssh -t” กำลังถูกเปลี่ยนแปลง?


29

ฉันพยายามคัดลอกไฟล์ผ่าน 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 sys 0m0.005s

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

ขนาดไฟล์ทั้งสองมีขนาด 26 ไบต์ในกรณีนี้

เหตุใดไฟล์ขนาดเล็กอาจถ่ายโอนได้ดี แต่ไฟล์ขนาดใหญ่มีการเพิ่มจำนวนไบต์


10
เป็น-tตัวเลือกซึ่งแบ่งการถ่ายโอน อย่าใช้-tหรือ-Tยกเว้นว่าคุณต้องการเหตุผลพิเศษ ค่าเริ่มต้นใช้งานได้ในกรณีส่วนใหญ่ดังนั้นตัวเลือกเหล่านี้จึงไม่ค่อยจำเป็น
kasperd

3
ไม่เคยคิดว่าฉันจะพูดแบบนี้ในศตวรรษนี้ แต่คุณอาจต้องการลอง uuencode และ uudecode หากssh -t catเป็นวิธีเดียวที่จะถ่ายโอนไฟล์
Mark Plotnick

1
@MarkPlotnick รุ่นที่ทันสมัยของ uuencode / uudecode ตอนนี้มีชื่อว่า base64 / base64 -d
Archemar

คำตอบ:


60

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 ของคุณ


5

เมื่อใช้วิธีการนั้นในการคัดลอกไฟล์ไฟล์ต่าง ๆ

เซิร์ฟเวอร์ระยะไกล

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

เซิร์ฟเวอร์ท้องถิ่น

ใช้ssh ... catคำสั่งของคุณ:

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

ผลลัพธ์ในไฟล์นี้บนโลคัลเซิร์ฟเวอร์:

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

กำลังสืบสวนทำไม

การตรวจสอบไฟล์ที่ได้จากด้านในเครื่องจะแสดงว่าไฟล์นั้นเสียหาย หากคุณถอด-tสวิตช์ออกจากsshคำสั่งของคุณก็จะทำงานได้ตามที่คาดไว้

$ 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

Checksums ก็ใช้ได้เช่นกัน:

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

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

ขอบคุณซิม แม้ว่าในความเป็นจริงคุณเป็นคนแรกที่โพสต์คำตอบที่ถูกต้อง แต่ฉันเลือกStéphaneสำหรับคำตอบที่เลือกเนื่องจากความลึกของคำอธิบายของเขา ไม่ต้องกังวลคุณมีประวัติของโพสต์ที่ฉันเรียนรู้มานานและแน่นอนว่าฉันลบล้างโพสต์ที่ฉันเรียนรู้ ขอขอบคุณ.
dotancohen

@dotancohen - ไม่ต้องกังวลคุณยอมรับว่า A's ที่คุณรู้สึกว่าเป็นคนที่ช่วยคุณในฐานะ OP มากที่สุด 8-) ความสามารถของเขาในการอธิบายว่าทำไมสิ่งต่าง ๆ ถึงเกิดขึ้นนั้นไม่มีใครเทียบได้ยกเว้นโดยกิลส์
slm
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.