Warum wird diese Binärdatei, die über "ssh -t" übertragen wird, geändert?


29

Ich versuche, Dateien über SSH zu kopieren , kann sie jedoch nicht verwenden scp, da ich den genauen Dateinamen nicht kenne. Obwohl kleine Binärdateien und Textdateien problemlos übertragen werden, werden große Binärdateien geändert. Hier ist die Datei auf dem Server:

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

Hier ist die Datei, nachdem ich sie verschoben habe:

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

Beachten Sie, dass die heruntergeladene Datei größer ist als die auf dem Server! Wenn ich jedoch mit einer sehr kleinen Datei dasselbe mache, funktioniert alles wie erwartet:

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

echte 0m3.041s Benutzer 0m0.013s sys 0m0.005s

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

Beide Dateigrößen betragen in diesem Fall 26 Byte.

Warum können kleine Dateien problemlos übertragen werden, aber große Dateien werden mit einigen Bytes versehen?


10
Dies ist die -tOption, die die Übertragung unterbricht. Verwenden Sie nicht -toder -T, es sei denn, Sie benötigen sie aus einem bestimmten Grund. Die Standardeinstellung funktioniert in den allermeisten Fällen, sodass diese Optionen sehr selten benötigt werden.
Kasperd

3
Ich hätte nie gedacht, dass ich das in diesem Jahrhundert sagen würde, aber Sie könnten uuencode und uudecode ausprobieren, wenn dies ssh -t catdie einzige Möglichkeit ist, Dateien zu übertragen.
Mark Plotnick

1
@ MarkPlotnick moderne Version von Uuencode / Uudecode sind jetzt base64 / base64 -d
Archemar

Antworten:


60

TL; DR

Nicht verwenden -t. -tbeinhaltet ein Pseudoterminal auf dem Remote-Host und sollte nur zum Ausführen visueller Anwendungen von einem Terminal aus verwendet werden.

Erläuterung

Das Zeilenvorschubzeichen (auch als Zeilenvorschub oder als "Zeilenvorschub" bezeichnet \n) weist das Terminal an, den Cursor nach unten zu bewegen, wenn es an ein Terminal gesendet wird.

Wenn Sie jedoch seq 3in einem Terminal ausgeführt werden und dort auf etwas seqschreiben , sehen Sie nicht:1\n2\n3\n/dev/pts/0

1
 2
  3

aber

1
2
3

Warum das?

Tatsächlich sieht das Terminal , wenn seq 3(oder ssh host seq 3in diesem Fall) schreibt . Das heißt, die Zeilenvorschübe wurden in Wagenrücklauf (auf den Terminals den Cursor zurück nach links bewegen) und Zeilenvorschub übersetzt.1\n2\n3\n1\r\n2\r\n3\r\n

Das erledigt der Endgerätetreiber. Genauer gesagt, nach der Leitungsdisziplin des Endgeräts (oder Pseudo-Endgeräts) ein Softwaremodul, das sich im Kernel befindet.

Sie können das Verhalten dieser Zeilendisziplin mit dem sttyBefehl steuern . Die Übersetzung von LF-> CRLFwird mit eingeschaltet

stty onlcr

(die in der Regel standardmäßig aktiviert ist). Sie können es ausschalten mit:

stty -onlcr

Oder Sie können die gesamte Ausgabeverarbeitung ausschalten mit:

stty -opost

Wenn du das tust und rennst seq 3, siehst du:

$ stty -onlcr; seq 3
1
 2
  3

wie erwartet.

Nun, wenn Sie dies tun:

seq 3 > some-file

seqschreibt nicht mehr in ein Terminal, es wird in eine Datei geschrieben, es wird keine Übersetzung durchgeführt. Enthält some-filealso 1\n2\n3\n. Die Übersetzung erfolgt nur beim Schreiben auf ein Endgerät. Und es ist nur für die Anzeige gemacht.

in ähnlicher Weise, wenn Sie tun:

ssh host seq 3

sshschreibt, 1\n2\n3\nunabhängig davon ssh, wohin die Ausgabe geht.

Was tatsächlich passiert, ist, dass der seq 3Befehl hostmit seiner stdout ausgeführt wird, die an eine Pipe umgeleitet wird. Der sshServer auf dem Host liest das andere Ende der Pipe und sendet es über den verschlüsselten Kanal an Ihren sshClient. Der sshClient schreibt es auf seinen Standardausgang, in Ihrem Fall ein Pseudo-Endgerät, in das LFs CRLFzur Anzeige übersetzt werden.

Viele interaktive Anwendungen verhalten sich anders, wenn ihre Standardausgabe kein Terminal ist. Wenn Sie beispielsweise Folgendes ausführen:

ssh host vi

vimag es nicht, mag es nicht, wenn seine Ausgabe an eine Pipe geht. Es spricht nicht mit einem Gerät, das zum Beispiel die Escape-Sequenzen für die Cursorpositionierung versteht.

Also sshhat die -tOption dafür. Mit dieser Option erstellt der ssh-Server auf dem Host ein Pseudo-Terminal-Gerät und erstellt das stdout (und stdin und stderr) von vi. Was viauf diesem Endgerät schreibt, durchläuft diese Pseudo-Terminal-Leitungsdisziplin und wird vom sshServer gelesen und über den verschlüsselten Kanal an den sshClient gesendet . Es ist das gleiche wie zuvor , außer daß anstelle der Verwendung eines Rohres , das sshverwendet Server ein Pseudo-Terminal .

Der andere Unterschied besteht darin, dass der Client auf der sshClientseite das Terminal in den rawModus versetzt. Das bedeutet, dass dort keine Übersetzung durchgeführt wird ( opostist deaktiviert und auch andere eingabeseitige Verhaltensweisen). Wenn Sie beispielsweise ein Zeichen Ctrl-Ceingeben ssh, wird dieses ^CZeichen nicht unterbrochen, sondern an die entfernte Seite gesendet, wo die Leitungsdisziplin des entfernten Pseudoterminals den Interrupt an den entfernten Befehl sendet .

Wenn Sie das tun:

ssh -t host seq 3

seq 3schreibt 1\n2\n3\nauf sein stdout, das ein Pseudo-Terminal-Gerät ist. Wegen onlcr, die übersetzt wird auf dem Host zu 1\r\n2\r\n3\r\nund an Sie über den verschlüsselten Kanal. Auf Ihrer Seite gibt es keine Übersetzung ( onlcrdeaktiviert), daher 1\r\n2\r\n3\r\nwird diese (aufgrund des rawModus) unangetastet und korrekt auf dem Bildschirm Ihres Terminalemulators angezeigt.

Nun, wenn Sie dies tun:

ssh -t host seq 3 > some-file

Es gibt keinen Unterschied von oben. sshwerde das selbe schreiben:, 1\r\n2\r\n3\r\naber diesmal in some-file.

Also im Grunde alle LFin der Ausgabe seqhat übersetzt CRLFin some-file.

Es ist das gleiche, wenn Sie tun:

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

Alle LFZeichen (0x0a Bytes) werden in CRLF (0x0d 0x0a) übersetzt.

Das ist wahrscheinlich der Grund für die Beschädigung in Ihrer Datei. Im Fall der zweiten kleineren Datei kommt es einfach so vor, dass die Datei keine 0x0a-Bytes enthält, sodass keine Beschädigung vorliegt.

Beachten Sie, dass Sie mit unterschiedlichen Einstellungen für tty verschiedene Arten von Beschädigungen erhalten können. Eine weitere mögliche Art der Beschädigung -tbesteht darin, dass Ihre Startdateien auf host( ~/.bashrc, ~/.ssh/rc...) Dinge in ihre stderr schreiben, da -tstdout und stderr der Remote-Shell in sshstdout zusammengeführt werden (beide gehen ins Pseudo Endgerät).

Die Fernbedienung catsoll dort nicht auf ein Endgerät ausgegeben werden.

Sie wollen:

ssh host cat remote-file > local-file

Du könntest es tun:

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

Das würde funktionieren (außer in dem oben diskutierten Fall der Korruption beim Schreiben an stderr ), aber selbst das wäre suboptimal, da Sie diese unnötige Pseudo-Terminal-Schicht hätten host.


Noch mehr Spaß:

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

OKAY.

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

LF übersetzt nach CRLF

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

Wieder ok.

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

Dies ist eine andere Form der Ausgabe-Nachbearbeitung, die von der Terminalleitungsdisziplin durchgeführt werden kann.

$ 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

sshWeigert sich, dem Server die Verwendung eines Pseudoterminals mitzuteilen, wenn seine eigene Eingabe kein Terminal ist. Du kannst es erzwingen mit -tt:

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

Die Liniendisziplin leistet viel mehr auf der Eingabeseite.

Hier echowird weder die Eingabe gelesen noch aufgefordert, diese auszugeben. x\r\n\nWo kommt das her? Das ist das lokale echodes entfernten Pseudoterminals ( stty echo). Der sshServer leitet den x\nvom Client gelesenen Wert an die Masterseite des entfernten Pseudoterminals weiter. Und die Liniendisziplin von diesem spiegelt es wieder (bevor stty opostausgeführt wird, weshalb wir ein CRLFund nicht sehen LF). Das ist unabhängig davon, ob die Remote-Anwendung etwas von stdin liest oder nicht.

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

Das 0x3Zeichen wird als ^C( ^und C) zurückgemeldet, stty echoctlund die Shell und der Schlaf erhalten ein ZEICHEN, weil stty isig.

Also, während:

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

ist schon schlimm genug, aber

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

Dateien in die andere Richtung zu übertragen ist viel schlimmer. Sie werden einige CR bekommen -> LF Übersetzung, aber auch Probleme mit allen Sonderzeichen ( ^C, ^Z, ^D, ^?, ^S...) und auch die Fernbedienung catnicht EOF sehen , wenn das Ende local-fileerreicht ist, nur dann , wenn ^Dnach einem gesendet wird \r, \noder ein anderes ^Dwie cat > filein Ihrem Terminal.


5

Wenn Sie diese Methode zum Kopieren der Datei verwenden, scheinen die Dateien unterschiedlich zu sein.

Remote-Server

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

Lokaler Server

Führen Sie Ihren ssh ... catBefehl aus:

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

Ergebnisse in dieser Datei auf dem lokalen Server:

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

Warum untersuchen?

Die Untersuchung der resultierenden Datei auf der lokalen Seite zeigt, dass sie beschädigt ist. Wenn Sie den -tSchalter aus Ihrem sshBefehl entfernen, funktioniert er wie erwartet.

$ 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

Checksummen funktionieren jetzt auch:

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

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

Vielen Dank, Sim. Obwohl Sie tatsächlich der erste waren, der die richtige Antwort gepostet hat, habe ich Stéphane aufgrund der Tiefe seiner Erklärung für die gewählte Antwort ausgewählt. Keine Sorge, Sie haben eine lange Post-Historie, von der ich lerne, und natürlich habe ich die Posts, von denen ich lerne, positiv bewertet. Vielen Dank.
Dotancohen

@dotancohen - keine Sorge, du akzeptierst, welche A's du fühlst, die helfen dir als OP am meisten 8-). Seine Fähigkeit zu erklären, warum etwas passiert, ist unerreicht, außer von Gilles.
SLM
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.