[unix] “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 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 시스템 0m0.005s

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

이 경우 두 파일 크기는 모두 26 바이트입니다.

작은 파일은 잘 전송되지만 큰 파일에는 약간의 바이트가 추가되는 이유는 무엇입니까?



답변

TL; DR

를 사용하지 마십시오 -t. -t원격 호스트의 의사 터미널을 포함하며 터미널에서 시각적 응용 프로그램을 실행하는 데만 사용해야합니다.

설명

줄 바꿈 문자 (줄 바꾸기 또는이라고도 함 \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. 즉, 줄 바꿈이 캐리지 리턴 (어떤 터미널에서 커서를 화면 왼쪽으로 다시 이동시키는 지) 및 줄 바꿈으로 변환되었습니다.

이것은 터미널 장치 드라이버에 의해 수행됩니다. 보다 정확히 말하면, 터미널 (또는 의사 터미널) 장치의 회선 분야에서 커널에 상주하는 소프트웨어 모듈입니다.

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있습니다.

실제로 발생하는 것은 stdout이 파이프로 경로 재 지정된 상태 에서 seq 3명령이 실행되는 것 host입니다. 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 31\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\n, 그러나로이 시간을 some-file.

따라서 기본적으로의 모든 LF출력 seq은로 변환 CRLF되었습니다 some-file.

당신이하는 경우에도 동일합니다 :

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

모든 LF문자 (0x0a 바이트)가 CRLF (0x0d 0x0a)로 변환됩니다.

아마도 파일이 손상된 이유 일 것입니다. 두 번째 작은 파일의 경우 파일에 0x0a 바이트가 포함되어 있지 않으므로 손상이 없습니다.

다른 tty 설정으로 다른 유형의 손상이 발생할 수 있습니다. 과 관련된 부패의 또 다른 잠재적 인 유형 -t인 경우에 당신의 시작 파일 host( ~/.bashrc, ~/.ssh/rc…) 때문에 그들의 표준 에러에 대한 쓰기 것들 -t에 병합되는 최대 stdout 및 원격 쉘 끝의 열려진 ssh표준 출력의 (의사로 이동 둘 터미널 장치).

리모컨 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실행 하기 전에 우리는 a 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

다른 방법으로 파일을 전송하는 것은 훨씬 더 나쁩니다. 모든 특수 문자> LF 번역뿐만 아니라 문제 (- 당신은 몇 가지 CR 얻을 것이다 ^C, ^Z, ^D, ^?, ^S…) 또한 리모콘 cat의 마지막 때 EOF 표시되지 않습니다 local-file에 도달 할 때이 경우에만 ^D이후에 전송되는 정보 \r, \n또는 터미널에서 할 ^D때와 같은 다른 것 cat > file.


답변

해당 방법을 사용하여 파일을 복사 할 때 파일이 다른 것처럼 보입니다.

원격 서버

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


답변