Estou tentando copiar arquivos pelo SSH , mas não posso usá-lo scp
por não saber o nome exato do arquivo que preciso. Embora pequenos arquivos binários e arquivos de texto sejam transferidos corretamente, arquivos binários grandes são alterados. Aqui está o arquivo no servidor:
remote$ ls -la
-rw-rw-r-- 1 user user 244970907 Aug 24 11:11 foo.gz
remote$ md5sum foo.gz
9b5a44dad9d129bab52cbc6d806e7fda foo.gz
Aqui está o arquivo depois que eu o movi:
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
Observe que o arquivo baixado é maior que o do servidor! No entanto, se eu fizer o mesmo com um arquivo muito pequeno, tudo funcionará conforme o esperado:
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
usuário 0m3.041s real 0m0.013s sys 0m0.005s
local$ md5sum hi.txt.gz
08bf5080733d46a47d339520176b9211 hi.txt.gz
Ambos os tamanhos de arquivo são 26 bytes neste caso.
Por que arquivos pequenos podem ser transferidos corretamente, mas arquivos grandes recebem alguns bytes adicionados a eles?
-t
opção, que interrompe a transferência. Não use-t
ou-T
, a menos que você precise deles por um motivo muito específico. O padrão funciona na grande maioria dos casos, portanto, essas opções raramente são necessárias.ssh -t cat
for a única maneira de transferir arquivos.Respostas:
TL; DR
Não use
-t
.-t
envolve um pseudo-terminal no host remoto e deve ser usado apenas para executar aplicativos visuais a partir de um terminal.Explicação
O caractere de avanço de linha (também conhecido como nova linha ou
\n
) é aquele que, quando enviado para um terminal, informa ao terminal para mover o cursor para baixo.No entanto, quando você executa
seq 3
um terminal, é aí queseq
escreve1\n2\n3\n
para algo como/dev/pts/0
, você não vê:mas
Por que é que?
Na verdade, quando
seq 3
(oussh host seq 3
nesse caso) escreve1\n2\n3\n
, o terminal vê1\r\n2\r\n3\r\n
. Ou seja, os feeds de linha foram traduzidos para retorno de carro (no qual os terminais movem o cursor de volta para a esquerda da tela) e feed de linha.Isso é feito pelo driver de dispositivo do terminal. Mais exatamente, pela disciplina de linha do dispositivo terminal (ou pseudo-terminal), um módulo de software que reside no kernel.
Você pode controlar o comportamento dessa disciplina de linha com o
stty
comando A tradução deLF
->CRLF
é ativada com(que geralmente é ativado por padrão). Você pode desativá-lo com:
Ou você pode desativar todo o processamento de saída com:
Se você fizer isso e executar
seq 3
, verá:como esperado.
Agora, quando você faz:
seq
não está mais gravando em um terminal, está gravando em um arquivo, não há tradução sendo feita. Osome-file
mesmo contém1\n2\n3\n
. A tradução é feita apenas ao gravar em um dispositivo terminal. E isso é feito apenas para exibição.Da mesma forma, quando você faz:
ssh
está gravando,1\n2\n3\n
independentemente dossh
resultado da saída.O que realmente acontece é que o
seq 3
comando é executadohost
com seu stdout redirecionado para um canal. Ossh
servidor no host lê a outra extremidade do canal e envia-o pelo canal criptografado para o seussh
cliente e ossh
cliente grava no stdout, no seu caso, um dispositivo pseudo-terminal, para o qualLF
são traduzidosCRLF
para exibição.Muitos aplicativos interativos se comportam de maneira diferente quando o stdout não é um terminal. Por exemplo, se você executar:
vi
não gosta, não gosta que sua saída vá para um cano. Ele acha que não está falando com um dispositivo capaz de entender as seqüências de escape do posicionamento do cursor, por exemplo.Então
ssh
tem a-t
opção para isso. Com essa opção, o servidor ssh no host cria um dispositivo pseudo-terminal e torna esse stdout (e stdin e stderr) devi
. O quevi
escreve nesse dispositivo terminal passa por essa disciplina remota de linha pseudo-terminal e é lido pelossh
servidor e enviado pelo canal criptografado para ossh
cliente. É o mesmo de antes, exceto que, em vez de usar um pipe , ossh
servidor usa um pseudo-terminal .A outra diferença é que, no lado do
ssh
cliente , o cliente define o terminal noraw
modo. Isso significa que nenhuma tradução é feita lá (opost
está desativada e também outros comportamentos do lado da entrada). Por exemplo, quando você digita Ctrl-C, em vez de interromperssh
, esse^C
caractere é enviado para o lado remoto, onde a disciplina de linha do pseudo-terminal remoto envia a interrupção para o comando remoto.Quando você faz:
seq 3
grava1\n2\n3\n
em seu stdout, que é um dispositivo pseudo-terminal. Por causa doonlcr
que é traduzido no host para1\r\n2\r\n3\r\n
e enviado a você sobre o canal criptografado. Do seu lado, não há tradução (onlcr
desativada); portanto,1\r\n2\r\n3\r\n
é exibida intocada (por causa doraw
modo) e corretamente na tela do seu emulador de terminal.Agora, se você fizer:
Não há diferença de cima.
ssh
vai escrever a mesma coisa:,1\r\n2\r\n3\r\n
mas desta vez emsome-file
.Então, basicamente todo o
LF
na saída deseq
ter sido traduzida paraCRLF
dentrosome-file
.É o mesmo se você fizer:
Todos os
LF
caracteres (0x0a bytes) estão sendo convertidos em CRLF (0x0d 0x0a).Essa é provavelmente a razão da corrupção no seu arquivo. No caso do segundo arquivo menor, acontece que o arquivo não contém 0x0a bytes, portanto, não há corrupção.
Observe que você pode obter diferentes tipos de corrupção com diferentes configurações de tty. Outro tipo potencial de corrupção associado
-t
é se os arquivos de inicialização emhost
(~/.bashrc
,~/.ssh/rc
...) gravam coisas no stderr, porque com-t
o stdout e o stderr do shell remoto acabam sendo mesclados nossh
stdout do s (ambos vão para o pseudo dispositivo terminal).Você não deseja que o controle remoto
cat
saia para um dispositivo terminal lá.Você quer:
Você poderia fazer:
Isso funcionaria (exceto na gravação do caso stderr de corrupção discutido acima), mas mesmo isso seria sub-ideal, pois você teria a camada pseudo-terminal desnecessária em execução
host
.Um pouco mais divertido:
ESTÁ BEM.
LF
traduzido paraCRLF
OK de novo.
Essa é outra forma de pós-processamento de saída que pode ser feita pela disciplina da linha de terminal.
ssh
se recusa a dizer ao servidor para usar um pseudo-terminal quando sua própria entrada não é um terminal. Você pode forçá-lo com-tt
:A disciplina de linha faz muito mais no lado da entrada.
Aqui,
echo
não lê sua entrada nem foi solicitado que a produza;x\r\n\n
portanto, de onde ela vem? Esse é o localecho
do pseudo-terminal remoto (stty echo
). Ossh
servidor está alimentando ax\n
leitura do cliente para o lado principal do pseudo-terminal remoto. E a disciplina de linha disso ecoa de volta (antesstty opost
é executada e é por isso que vemos umCRLF
e nãoLF
). Isso é independente de o aplicativo remoto ler algo do stdin ou não.O
0x3
personagem é repetido como^C
(^
eC
) por causa destty echoctl
e a concha e o sono recebem um SIGINT porquestty isig
.Por enquanto:
já é ruim o suficiente, mas
transferir arquivos do outro lado é muito pior. Você terá alguns CR -> Tradução LF, mas também problemas com todos os caracteres especiais (
^C
,^Z
,^D
,^?
,^S
...) e também o controle remotocat
não verá EOF quando o fimlocal-file
é alcançado, somente quando^D
é enviado depois de um\r
,\n
ou outro^D
como ao fazercat > file
no seu terminal.fonte
Ao usar esse método para copiar o arquivo, os arquivos parecem diferentes.
Servidor remoto
Servidor local
Executando seu
ssh ... cat
comando:Resultados neste arquivo no servidor local:
Investigando por quê?
Investigar o arquivo resultante no lado local mostra que ele foi corrompido. Se você
-t
desativar ossh
comando, ele funcionará conforme o esperado.As somas de verificação agora também funcionam:
fonte