「ssh -t」で転送されるバイナリファイルが変更されるのはなぜですか?

「ssh -t」で転送されるバイナリファイルが変更されるのはなぜですか?

頑張ってる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 [email protected] -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 [email protected] -t 'cat /path/to/hello.txt.gz' > hi.txt.gz

実際の0m3.041sユーザー0m0.013sシステム0m0.005s

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

この例では、両方のファイルのサイズは26バイトです。

小さなファイルはうまく転送されますが、大きなファイルはいくつかのバイトを追加するのですか?

答え1

長い話を短く

を使用しないでください-t-tリモートホストの擬似端末に関連しており、端末でビジュアルアプリケーションを実行するためにのみ使用する必要があります。

説明する

改行文字(newlineまたは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端末はを見ます1\r\n2\r\n3\r\n。つまり、改行文字はキャリッジリターン文字(この時点で端末がカーソルを画面の左側に戻す)と改行文字に変換されます。

これは、端末装置ドライバによって行われる。より正確には、端末(または疑似端末)デバイスの接続規則に従ってカーネルに常駐するソフトウェアモジュールです。

コマンドを使用して、この線規則の動作を制御できますsttyLF->翻訳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コマンドが実行されるとhoststdoutがパイプにリダイレクトされることです。sshホストのサーバーはパイプのもう一方の端を読み取り、それを暗号化されたチャネルを介してクライアントに送信しますssh。クライアントsshはこれをstdout(あなたの場合は疑似端末デバイス)に書き込むことで、LFsがCRLF表示されるように変換されます。

多くのインタラクティブなアプリケーションは、標準出力が端末でない場合、異なる動作をします。たとえば、次を実行する場合:

ssh host vi

viそれは気に入らず、出力がパイプに入るのが好きではありません。たとえば、カーソル位置決めエスケープシーケンスを理解するデバイスと通信しないとします。

だからそのオプションがsshあります-t。このオプションを有効にすると、ホスト上のSSHサーバーは疑似端末装置を作成し、それを疑似端末装置として使用しますvi。端末装置に記録されたコンテンツは、viリモート擬似端末回線規則を経てサーバーから読み取られ、暗号化されたチャネルを介しsshssh管路sshサーバーは擬似端末

別の違いは、クライアント側でsshクライアントが端末をrawモードに設定し、ローカルを無効にすることです。エコ)。これは翻訳が行われないことを意味します(opost無効化および他の入力側の動作)。たとえば、入力するとその文字がリモートエンドに送信され、Ctrl-C中断するのではなくリモート疑似端末の行ルールが実行されます。ssh^C邪魔するリモートコマンドで。

これを行うとき:

ssh -t host seq 3

seq 31\n2\n3\n擬似端末装置である標準出力に書き込みます。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\n、しかし今回はsome-file

したがって、基本的にすべてのLF出力seqは。CRLFsome-file

これにより結果は同じになります。

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

すべてのLF文字(0x0aバイト)はCRLF(0x0d 0x0a)に変換されます。

これがファイルが破損した理由かもしれません。 2番目の小さなファイルでは、ファイルに0x0aバイトが含まれていないため、破損はありません。

異なる tty 設定により、他の種類の損傷が発生する可能性があります。これに関連する別の潜在的な破損の種類は、(-t、...)の起動ファイルがstderrに何かを書き込む場合です。これは、リモートシェルのstdoutとstderrが最終的にstdoutにマージされるためです(どちらも擬似端末デバイスに移動します)。host~/.bashrc~/.ssh/rc-tssh

catリモコン出力をエンドデバイスに転送したくありません。

あなたが望むもの:

ssh host cat remote-file > local-file

次のことができます。

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

これでうまくいきます(次の場合は除く)。標準エラーに書き込む上記で議論したダメージ)、しかしこれも最適ではありません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

sshReject は、サーバー自体の入力が端末でない場合に擬似端末を使用するようサーバーに指示します。-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その行ルールはこれを反映しています(beforestty opostがrunなので、CRLF代わりにaが表示されますLF)。リモートアプリケーションがstdinから何でも読み取るかどうかは問題ではありません。

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

なぜなら、この文字は0x3(and)でエコーされ、シェルとスリープはSIGINTを受け取るからです。^C^Cstty echoctlstty isig

だから同時に:

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

十分悪いけど、

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

ファイルを反対方向に転送する方がはるかに悪いです。 CR - > LF変換が行われますが、すべての特殊文字(^C、、、、、... )にも問題があり、eofの終わりに達すると、リモートデバイスは次の場合にのみeof^Zを表示できます。端末で行うのと同様の操作の後に送信します。^D^?^Scatlocal-file^D\r\n^Dcat > file

答え2

この方法を使用してファイルをコピーすると、ファイルが異なるように見えます。

リモートサーバー

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

これでチェックサムも動作します。

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

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

関連情報