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í que seq
escreve 1\n2\n3\n
para algo como /dev/pts/0
, você não vê:
1
2
3
mas
1
2
3
Por que é que?
Na verdade, quando seq 3
(ou ssh host seq 3
nesse caso) escreve 1\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 de LF
-> CRLF
é ativada com
stty onlcr
(que geralmente é ativado por padrão). Você pode desativá-lo com:
stty -onlcr
Ou você pode desativar todo o processamento de saída com:
stty -opost
Se você fizer isso e executar seq 3
, verá:
$ stty -onlcr; seq 3
1
2
3
como esperado.
Agora, quando você faz:
seq 3 > some-file
seq
não está mais gravando em um terminal, está gravando em um arquivo, não há tradução sendo feita. O some-file
mesmo contém 1\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 host seq 3
ssh
está gravando, 1\n2\n3\n
independentemente do ssh
resultado da saída.
O que realmente acontece é que o seq 3
comando é executado host
com seu stdout redirecionado para um canal. O ssh
servidor no host lê a outra extremidade do canal e envia-o pelo canal criptografado para o seu ssh
cliente e o ssh
cliente grava no stdout, no seu caso, um dispositivo pseudo-terminal, para o qual LF
são traduzidos CRLF
para exibição.
Muitos aplicativos interativos se comportam de maneira diferente quando o stdout não é um terminal. Por exemplo, se você executar:
ssh host vi
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) de vi
. O que vi
escreve nesse dispositivo terminal passa por essa disciplina remota de linha pseudo-terminal e é lido pelo ssh
servidor e enviado pelo canal criptografado para o ssh
cliente. É o mesmo de antes, exceto que, em vez de usar um pipe , o ssh
servidor usa um pseudo-terminal .
A outra diferença é que, no lado do ssh
cliente , o cliente define o terminal no raw
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 interromper ssh
, 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:
ssh -t host seq 3
seq 3
grava 1\n2\n3\n
em seu stdout, que é um dispositivo pseudo-terminal. Por causa do onlcr
que é traduzido no host para 1\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 do raw
modo) e corretamente na tela do seu emulador de terminal.
Agora, se você fizer:
ssh -t host seq 3 > some-file
Não há diferença de cima. ssh
vai escrever a mesma coisa:, 1\r\n2\r\n3\r\n
mas desta vez em some-file
.
Então, basicamente todo o LF
na saída de seq
ter sido traduzida para CRLF
dentro some-file
.
É o mesmo se você fizer:
ssh -t host cat remote-file > local-file
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 em host
( ~/.bashrc
, ~/.ssh/rc
...) gravam coisas no stderr, porque com -t
o stdout e o stderr do shell remoto acabam sendo mesclados no ssh
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:
ssh host cat remote-file > local-file
Você poderia fazer:
ssh -t host 'stty -opost; cat remote-file` > local-file
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:
$ ssh localhost echo | od -tx1
0000000 0a
0000001
ESTÁ BEM.
$ ssh -t localhost echo | od -tx1
0000000 0d 0a
0000002
LF
traduzido para CRLF
$ ssh -t localhost 'stty -opost; echo' | od -tx1
0000000 0a
0000001
OK de novo.
$ ssh -t localhost 'stty olcuc; echo x'
X
Essa é outra forma de pós-processamento de saída que pode ser feita pela disciplina da linha de terminal.
$ 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
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
:
$ echo x | ssh -tt localhost 'stty -opost; echo' | od -tx1
0000000 x \r \n \n
0000004
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 local echo
do pseudo-terminal remoto ( stty echo
). O ssh
servidor está alimentando a x\n
leitura do cliente para o lado principal do pseudo-terminal remoto. E a disciplina de linha disso ecoa de volta (antes stty opost
é executada e é por isso que vemos um CRLF
e não LF
). Isso é independente de o aplicativo remoto ler algo do stdin ou não.
$ (sleep 1; printf '\03') | ssh -tt localhost 'trap "echo ouch" INT; sleep 2'
^Couch
O 0x3
personagem é repetido como ^C
( ^
e C
) por causa de stty echoctl
e a concha e o sono recebem um SIGINT porque stty isig
.
Por enquanto:
ssh -t host cat remote-file > local-file
já é ruim o suficiente, mas
ssh -tt host 'cat > remote-file' < local-file
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 remoto cat
não verá EOF quando o fim local-file
é alcançado, somente quando ^D
é enviado depois de um \r
, \n
ou outro ^D
como ao fazer cat > file
no seu terminal.
-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.