"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. 즉, 개행 문자는 캐리지 리턴 문자(이 시점에서 터미널이 커서를 화면 왼쪽으로 다시 이동함)와 개행 문자로 변환됩니다.

이는 터미널 장치 드라이버에 의해 수행됩니다. 보다 정확하게는 터미널(또는 의사 터미널) 장치의 연결 규칙에 따라 커널에 상주하는 소프트웨어 모듈입니다.

명령을 사용하여 이 선 규칙의 동작을 제어할 수 있습니다 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명령이 실행될 때 hoststdout이 파이프로 리디렉션된다는 것입니다. ssh호스트의 서버는 파이프의 다른 쪽 끝을 읽고 이를 암호화된 채널을 통해 클라이언트로 보냅니다 ssh. 클라이언트 ssh는 이를 stdout(귀하의 경우 의사 터미널 장치)에 기록하여 LFs가 CRLF표시되도록 변환됩니다.

많은 대화형 응용 프로그램은 표준 출력이 터미널이 아닐 때 다르게 동작합니다. 예를 들어 다음을 실행하는 경우:

ssh host vi

vi그것은 마음에 들지 않으며 출력이 파이프로 들어가는 것을 좋아하지 않습니다. 예를 들어 커서 위치 지정 이스케이프 시퀀스를 이해하는 장치와 통신하지 않는다고 생각합니다.

그래서 그 옵션이 ssh있습니다 -t. 이 옵션을 사용하면 호스트의 SSH 서버는 의사 터미널 장치를 생성하고 이를 의사 터미널 장치로 사용합니다 vi. 터미널 장치에 기록된 콘텐츠는 vi원격 의사 터미널 회선 규칙을 거쳐 서버에서 읽혀 지고 ssh암호화된 채널을 통해ssh관로, 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)로 변환됩니다.

이것이 파일이 손상된 이유일 수 있습니다. 두 번째 작은 파일의 경우 파일에 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해당 행 규칙은 이를 반영합니다( before stty opost가 run 이므로 CRLF대신 a 가 표시됩니다 LF). 원격 애플리케이션이 stdin에서 무엇이든 읽는지 여부는 중요하지 않습니다.

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

왜냐하면 이 문자는 0x3(and)로 에코되고 shell과 sleep은 SIGINT를 수신하기 때문입니다.^C^Cstty echoctlstty isig

그래서 동시에:

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

충분히 나쁘지만,

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

파일을 반대 방향으로 전송하는 것은 훨씬 더 나쁩니다. CR -> LF 변환이 이루어지지만 모든 특수 문자( ^C, ^Z, ^D, ^?, ^S...) 에도 문제가 있으며 cateof 끝에 도달하면 원격 장치는 local-file다음과 같은 경우 에만 eof를 볼 수 있습니다. ^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

관련 정보