Por que ls e hexdump discordam sobre o tamanho do meu arquivo?

4

Eu tenho um arquivo que criei (no vim), para fins de teste (teste de saída UTF-8 em um cliente SSH). Coisas estranhas, no entanto, estão acontecendo com esse arquivo.

Eu queria saber quais bytes estavam no arquivo, então usei hexdump :

username@computername:~$ hexdump -x intl.txt
0000000    9ecf    000a
0000003

Ok, há quatro bytes lá, como o 00 e o 0a entraram lá, não estou claro, mas que seja. Aqui é onde fica esquisito:

username@computername:~$ ls -al intl.txt
-rw-rw-r-- 1 username username 3 Mar 26 15:14 intl.txt

Espere, são três bytes? O que está acontecendo aqui?

Como se isso não fosse estranho o suficiente, hexdump -C fornece uma saída muito diferente:

username@computername:~$ hexdump -C intl.txt
00000000  cf 9e 0a                                          |...|
00000003

O Vim também está um pouco confuso sobre o arquivo. Quando eu inicio, ele dá isso na linha de status:

"intl.txt" 1L, 3C

No topo, no entanto, eu recebo isso (usando set list ):

Ϟ$
~
~
~
~

Então, ele acha que há três caracteres, mas só imprime um. Eu poderia entender se ele imprimiu o koppa e uma linha em branco abaixo dele ...

    
por Mark 26.03.2014 / 22:33

3 respostas

6

Como outros apontaram, isso ocorre porque hexdump -x trata os arquivos como contendo palavras de 2 bytes. Nos sistemas little endian (quase todos os desktops são), isso significa que os bytes serão trocados antes de serem exibidos. Isso significa que os valores de byte são impressos em pares e que a ordem desses bytes é trocada. Como você tem um número ímpar de bytes, hexdump adiciona um zero para compor o par final. O zero é então trocado com o 0a . Este é um comportamento documentado para hexdump , por isso não está mentindo para você!

Usar hexdump -C é um comando melhor para obter uma saída formatada que mostre os bytes na ordem em que estão no arquivo. Além disso, o 0a é uma nova linha e provavelmente foi adicionado silenciosamente ao criar o arquivo ( vim faz isso por padrão). Por exemplo, echo sempre adicionará uma nova linha se você não disser não. Em bash :

echo -e '\xcf\x9e' | hexdump -C

fornecerá o mesmo resultado, mas a supressão da nova linha com -n dará o que você esperava:

echo -ne '\xcf\x9e' | hexdump -C

Para impedir que vim adicione a nova linha:

:set noeol
:set binary
    
por 26.03.2014 / 23:13
2

Se você está tendo problemas para entender endianess, aqui está outra ilustração.

#include <stdio.h>
#include <inttypes.h>
#include <unistd.h>

int main (void) {
    uint16_t x = 1;
    write(1, &x, 2);
    x = 2;
    write(1, &x, 2);
    return 0;
}  

Este é o código C que escreve 2 valores de 16 bits, 1 e 2. Quando pensamos em valores, pensamos neles como big endian, então o preenchimento aqui (para fazer estes valores de 16 bits) significaria você tem um byte de zero e um byte no valor de 1 (ou 2). No entanto, como o sistema é little endian e aqui considera essas duas unidades discretas de 16 bits (2 bytes), os quatro bytes que literalmente são gravados são 1, 0, 2, 0.

Se você compilar ( gcc whatever.c ) e redirecionar para um arquivo ( ./a.out > dword ), hexdump -C mostrará a ordem física dos bytes:

> hexdump -C dword
00000000  01 00 02 00  |....|
00000004

Mas, neste caso, hexdump -x fornecerá uma interpretação mais correta em termos de significado, porque troca os bytes para mostrar os dois valores corretos de 16 bits:

> hexdump -x dword
0000000    0001    0002                                                
0000004

Se esses quatro bytes forem interpretados como um inteiro de 32 bits (little endian):

> hexdump -e '"%d\n"' dword
131073

Porque está traduzindo os seguintes 32 bits do binário em um valor decimal:

00000001 00000000 00000010 00000000

Como um valor big endian , seria 2 ^ 9 (512) + 2 ^ 24 (16777216). Isto é o que quero dizer com "pensar" em grande endianess. Se escrevermos um número binário, usamos big endian ordem de bits (um byte 00000010 == 2) e assim quando o número for maior que um byte, usaríamos big endian byte ordem (dois bytes 0000000000000010 == 2).

Mas como o sistema é little endian, 1 se quiséssemos escrever esses bytes como um número binário preenchido em 32 lugares (com os mesmos espaços a cada 8 dígitos para legibilidade), tem:

00000000 00000010 00000000 00000001

Em decimal, 2 ^ 17 (131072) + 2 ^ 0 (1). E, de fato, se você substituir o corpo do programa por:

int main (void) {
    uint32_t x = 131073;
    write(1, &x, 4);
    return 0;
}  

Compile, e escreva em um arquivo, você obterá exatamente a mesma saída de hexdump como antes, porque o arquivo contém exatamente a mesma coisa.

1. Observe que, quando falamos de endianess, praticamente sempre se refere à ordem de bytes. Como a menor unidade é efetivamente o byte, sua ordem de bits é irrelevante.

    
por 27.03.2014 / 00:17
1

hexdump -x exibe os valores como se fossem inteiros de 2 bytes. Em uma máquina little-endian , ela exibirá cada par de bytes na ordem trocada, tratando-os como dois bytes quantidades de bytes com o primeiro byte de alta ordem (segundo), seguido pelo byte de primeira ordem (primeiro).

Como você viu, usar hexdump -C exibe os bytes reais. O conteúdo real do seu arquivo são os dois bytes 0xCF 0x9E, seguidos pelo caractere de nova linha 0x0A. Vim e ls estão informando corretamente que existem 3 bytes (2 caracteres). Os dois primeiros bytes compreendem um caractere Unicode usando a codificação UTF-8.

Mais informações interessantes estão nos comentários acima.

    
por 26.03.2014 / 23:22