为什么 ls 和 hexdump 对我的文件大小不一致?

为什么 ls 和 hexdump 对我的文件大小不一致?

我创建了一个文件(在 vim 中),用于测试目的(在 SSH 客户端中测试 UTF-8 输出)。然而,这个文件发生了奇怪的事情。

我想知道文件中有哪些字节,所以我使用了hexdump

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

好吧,那里有四个字节,00 和 0a 是如何进入那里的,我不清楚,但无论如何。不过,这就是奇怪的地方:

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

等等,是三个字节?这里发生了什么?

好像这还不够奇怪,hexdump -C给出了非常不同的输出:

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

Vim 对这个文件也有点困惑。当我启动它时,它会在状态行中给出以下内容:

"intl.txt" 1L, 3C

然而,在顶部,我得到了这个(使用set list):

Ϟ$
~
~
~
~

因此,它认为有 3 个字符,但只打印了 1 个。如果它打印了 koppa 和它下面的空行,我可以理解......

答案1

正如其他人所指出的,这是因为hexdump -x将文件视为包含 2 字节字。在小端系统(几乎所有桌面都是),这意味着字节在显示之前将被交换。这意味着字节值是成对打印的,并且这些字节的顺序被交换。由于字节数为奇数,因此hexdump只需添加一个零即可组成最终的一对。然后将零与 交换0a。这是有记录的行为hexdump,所以它不是在骗你!

使用hexdump -C是一个更好的命令来获取格式化输出,该输出按字节在文件中的顺序显示字节。另外,这0a是一个新行,可能是由创建文件的任何内容悄悄添加的(vim默认情况下这样做)。例如,echo如果您不告诉它不要这样做,则始终会添加新行。在bash

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

将给出相同的结果,但抑制换行符-n将给出您所期望的结果:

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

要停止vim添加换行符:

:set noeol
:set binary

答案2

hexdump -x将值显示为 2 字节整数。上一个小尾数法机器这将以交换的顺序显示每对字节,将它们视为双字节数量,首先是高位(第二个)字节,然后是低位(第一个)字节。

如您所见,使用hexdump -C显示实际字节。文件的实际内容是两个字节 0xCF 0x9E,后跟换行符 0x0A。 Vim并且ls正确地告诉您有 3 个字节(2 个字符)。前两个字节包含一个使用 UTF-8 编码的 Unicode 字符。

更多有趣的信息在上面的评论中。

答案3

如果您无法理解字节顺序,请参阅另一个示例。

#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;
}  

这是 C 代码,它将写出 2 个 16 位值 1 和 2。当我们考虑值时,我们将它们视为大端,因此这里的填充(以生成这些 16 位值)意味着您有一个零字节然后是一个值 1(或 2)的字节。然而,由于该系统是小端这里考虑这两个离散的 16 位(2 字节)单元,实际写出的四个字节是 1、0、2、0。

如果您编译该 ( gcc whatever.c) 并重定向到文件 ( ./a.out > dword),hexdump -C将显示字节的物理顺序:

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

但在这种情况下,hexdump -x将在含义方面提供更正确的解释,因为它交换字节以显示正确的两个 16 位值:

> hexdump -x dword
0000000    0001    0002                                                
0000004

如果这四个字节被解释为(小端)32 位整数:

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

因为它正在将以下 32 位二进制值转换为十进制值:

00000001 00000000 00000010 00000000

作为一个大尾数法值,即 2^9 (512) + 2^24 (16777216)。这就是我所说的我们以大字节顺序“思考”的意思。如果我们写出一个二进制数,我们使用大尾数法位顺序(一个字节00000010== 2)所以当数字长于一个字节时,我们将使用大端字节顺序(两个字节0000000000000010== 2)。

但由于系统是小端序,1如果我们想将这些字节写为二进制数,填充到 32 个位置(为了可读性,每 8 位使用相同的空格),我们将有:

00000000 00000010 00000000 00000001

以十进制表示,2^17 (131072) + 2^0 (1)。事实上,如果将程序主体替换为:

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

编译并写入文件,你会得到完全相同的输出hexdump以前一样,因为该文件包含完全相同的内容。

1. 请注意,当我们谈论字节顺序时,它实际上总是指字节顺序。由于最小单位实际上是字节,因此其位顺序无关紧要。

相关内容