tr:超级工具:为数据做外科手术ZH

好文就拿过来 :D
引用:
http://www.linux-mag.com/2004-10/toc.html

Linux Magazine October 2004

Copyright Linux Magazine ©2004

POWER TOOLS
Performing Data Surgery
by Jerry Peek

转载并翻译自 linux magazine 网站,译文遵循 GNU FDL,仅正文部分可自由修改,重发布时正文之外的部分必须同时原样发布。译者对未告知作者表示歉意,但对误读的后果表示不负责。

超级工具: 为数据做外科手术
(作者 Jerry Peek)

正文开始

一年以前,2003 年 11 月的“超级工具”栏目 (可以在线查看,网址是http://www.linux-mag.com/2003-11/power_01.html ) 介绍了一些少有人知的文本编辑工具:行编辑器 ex 和 ed,以及流编辑器 sed

这个月,我们更深入一点,看看几乎无人知晓的工具 dd 的一些妙用。dd 的作用不仅是从磁带机读取数据,尽管那是它多年以来主要的用途。同时,我们会涉及到一些常用的东东,包括编辑工具 head 和 tail,八进制转储工具 od,以及设备 /dev/random 等等。

准备好了吗?

数据仅仅是数据而已

我们先看看 linux 是如何对付数据的。

通常,不管数据来自何处——磁盘,网络,磁带……——它们都仅仅是字节的序列而已。这意味着你不必关心如何从磁盘中读或者如何向磁带中写。你只要读写字节的序列就可以了,而设备驱动负责转换为设备的内部格式——磁盘区块,网络数据包等等。(但是,有时知道设备的块大小会很有好处,接下来我们也会遇到。)

只有一个数据格式问题是所有人都必须知道的:按行组织的数据和二进制数据的区别。

在按行组织的数据中,一个“行”是一个字符组成的序列,以新行符结束。(很多程序语言中以两个字符 "\n" 来表示某处是一个新行符,实际上在数据流中,它只占用一个字符的位置。)

例如,当 head 输出一个文件的头 10 行时,它从文件中读取一系列的字节,直到遇到十个新行符为止。它可能会调用十次某个库函数来做这件事,这个库函数知道如何从一个字节流中读取一行。下面是一个可以用来做实验的文本文件
引用:
What's In That File?

The od ("octal dump") utility displays a file in various formats. It's great for looking at a non-text file without messing up your terminal, or for seeing exactly what's in a text file.

For instance, let's use the Bourne shell's quoting operators to store three lines of text in a file named textfile. Then we'll dump it with od -c, which shows data in a character format:

$ echo "test
> 1
> 2" > textfile
>
$ od -c textfile
0000000 t e s t \n 1 \n 2 \n
0000011

Notice the three newlines in the file? A text file from a DOS-type system would have pairs of \r \n at the end of each line. The ending number 0000011 shows, in octal, how many bytes od displayed. (11 octal is 9 decimal, so we saw 9 bytes.)

Next let's look at the 50 bytes we read from /dev/urandom. We'll use an octal byte format (the first byte is 31 octal, the second is 62 octal, and so on):

$ od -b myrand
0000000 031 062 132 063 153 015 075 364 061 070
375 013 365 372 316 270
0000020 256 307 144 345 016 121 162 074 260 151
022 361 116 257 324 251
0000040 263 056 233 123 171 277 274 121 102 221
305 111 332 330 327 213
0000060 053 233
0000062

Using od -c instead shows that some of those random bytes are legal as characters, and others are not. This technique is useful for seeing what's in a file that doesn't seem to "look right" on your terminal.

二进制数据就是那些不按行组织的所有其他数据。它仅仅是一个字节序列。它也许包含内部结构,例如,很多文件利用文件开始处的特殊字符序列来标识自身,但是 linux 没有强制的文件类型,来告诉你数据究竟是如何组织的。

如上所说,数据可以来自网络连接,管道,键盘等等。如果数据在一个磁盘文件中,那么可以为它命名,在名称最后加上一个 . 和一个扩展名,来指示它是什么文件。例如,.txt 是文本文件,.jpg 是JPEG图像文件等等。一些应用程序要求命名服从这种约定。但是,大致上,linux 不关心这些。它只处理字节的序列。

数据……我只要一点点

如果使用 cat 这样的工具来查看文件,或者使用 shell 的重定向操作符 (|, < 还有 >) 来将来自某个设备或者应用程序的数据传输给另一个,你将一次得到所有数据,除非加以限制。

如果你只想看一个或多个文本文件的头几行,例如想知道文件里存了什么,可以用 head 工具。默认情况下,head 显示文件的头十行。如果要处理多个文件,就在每个文件前输出一个说明。可以调整它的行为,例如输出特定字节数而不是行数,使用命令行选项来控制。head 的语法在各个系统上可能不一样,运行 man head 来查看细节。tail 工具与此类似,但显示文件的最后几行,大多数版本的 tail 也可以处理多个文件。

类似其他设计优美的 linux 工具,head 和 tail 都可以从标准输入来读取信息,如果不指定文件名的话。这样允许你组合使用他们,像下面这个例子中一样

代码:

# prog | tee prog.out | tail ...last ten lines of prog output...


prog 的输出被 tee 接受,tee 将输入数据写到文件 prog.out 中,同时又将数据通过管道转送给 tail,接下来当 prog 结束后,tail 输出数据的最后十行。

head 和 tail 处理文本文件时工作得很好,但是处理任意文件就无能为力了。如果 head 和 tail 在终端上输出非文本的数据,你可能会看到一堆垃圾,还可能破坏显示。要避免这个问题,你可以将输出导向一个分页器,例如 less,它可以安全地显示不可打印字符。也可以将输出导向到 cat 工具,要加上 -v 选项。它们将除了换行和跳格之外的控制字符转换为双字符,类似 ^X,将其他非 ASCII 字符转换为 M- 加上它们的编码的低七位。

截断数据,第一部分

让我们先看看如何将长的数据流写为一系列短一些的文件。数据可能来自例如管道或者文件。

一种办法是使用 split。这个简单的工具读取数据,并写入为多个固定大小的文件。它写满一个文件再写另一个,直到所有输入都读写完毕。它很好用,例如当你要通过不可靠的网络传输一个大文件时,与现代的 ftp 不同,传输在中断后无法恢复,因此任何传输问题都意味着从头重传整个文件。这种情况下,最好将大文件分割成小文件,依次传输,只重传失败的那些,然后将小文件重新组装为完整的文件。

下面是一个例子。你要在办公室将 100MB 的文件 bigfile.tar.gz 分割成 100 个 1MB 的文件,然后在家里用拨号网络下载。可以用这个命令

代码:

office$ split -b 1m bigfile.tar.gz


现在你有了 100 个 1MB 的文件,名字默认是 xaa, xab, xac, ... xdu 和 xdv。然后你在家里下载了它们。要将他们重新组装,使用 shell 的通配符来匹配所有三字符的文件名,按照字母表的顺序:

代码:

home% cat x[a-d]? > bigfile.tar.gz


最好用 md5sum 或者 sum 这样的工具来校验一下,重新组装的文件 bigfile.tar.gz 与原始文件是一样的。

使用 dd 进行数据转储

dd 进行低级数据传送,按字节来,或者按块来,块的大小可以调节。它可以跳过输入或输出文件的指定个数的块,也可以转换数据格式。这些功能在与磁带或磁盘打交道时都非常有用,但是在很多数据传输时也很有用。

默认情况下,dd 从标准输入读取,写到标准输出。输入和输出文件名,以及其他选项的语法异于平常,必须用不带前导连字符 "-" 的方式给出。

例如,要读取软盘,将镜像写入一个文件,可以这样

代码:

$ dd if=/dev/fd0 of=dosboot.img 2880+0 records in 2880+0 records out $ ls -l dosboot.img -rw-rw-r- ... 1474560 Nov 2 12:59 dosboot.img


dd 命令行的意思是,从输入文件 /dev/fd0 读,将所有数据写入输出文件 dosboot.img。dd 不会查找数据或磁盘文件中的行,它进行二进制的复制,从第一个字节直到最后一个。dd 总是在标准错误告诉你,它读写了多少次。上面的例子中,它读了 2880 次 512 字节的块。如果不想看这些信息,或者任何错误信息,可以将 dd 的输出重定向到 Linux 的垃圾堆 /dev/null 中,只要在命令行最后添加 Bourne shell 的重定向操作符 2>/dev/null 就可以了。

指定较大的块会更有效率,设备驱动读写次数会更少。还有其他的选项,很多选项以 "conv=" 开始,例如 "conv=unblock" 可以将块中的尾部空格转换为一个新行符,"conv=swap" 将交换每对输入字节,在将某些磁带的数据写到其他硬件时需要这样做。但是我们在这里不讲这些,你可以自己去看手册页。我们来看一些不那么明显的用法。

愚蠢的把戏

需要一个包含 100 个任意字节的文件——例如,用于测试?Linux 设备文件 /dev/urandom (自 linux 1.3.30 起可用) 可以提供你需要的任意多的伪随机字节。要获取仅仅 100 字节,只要设置块大小为 1 字节,使用 "bs=1",然后让 dd 在读取 100 “块” 后停止:(读取了 100 字节)

代码:

dd if=/dev/urandom of=myrand bs=1 count=100


myrand 文件里面有什么?od 工具可以告诉你。(参见上面的 《What's in That File?》那篇文章)

如果你需要更多随机数据,就使用 /dev/random。但是从 /dev/random 读取要花好一阵子,手册页 random(4) 解释了为什么。当从 /dev/random 读取时,将块大小设为 1。

dd 的另一种用法,是在删除一个文本文件之前,将它的内容摧毁。简单地删除一个 linux 文件仅仅会删除指向数据的 inode 节点。我们可以在删除前,用 dd 来向文件中写入随机数据。通常 dd 在写入前会截断这个文件,因此我们必须用 "conv=notrunc" 来指明它覆盖已有的数据。设置 bs 为文件大小,count 为 1,例如:

代码:

% ls -l afile -rw------- ... 3769 Nov 2 13:41 afile % dd if=/dev/urandom of=afile \ bs=3769 count=1 conv=notrunc 1+0 records in 1+0 records out % rm afile


如果你想要的话,可以将摧毁内容的过程重复多次,使用 C shell 的 repeat 命令,Z shell 的 repeat 循环,或者直接用历史操作符 !!

截断数据,第二部分

如果需要依次发送一批数据,每次都要暂停并进行一些操作,split 不能满足需要。它只将数据写入文件,在所有文件都被写入之前不会停止。我们用 dd 来完成任务,但是需要一些 linux 如何处理数据流的知识。

2004 年 5 月 的专栏文章 《Great Command-line Combinations》(可以在线查看,网址是 http://www.linux-mag.com/2004-05/power_01.html ) 描述了一个 shell 循环,从流中读取数据,一行一行地读,直到所有数据都被读出。在那个例子中,我们使用 read 命令来读取 find 命令的输出的每一行。现在我们用类似的技术来读取 someprog 的输出,每次读取固定大小的块。基本的循环如下

代码:

someprog | while : do chunk=$(dd count=1 bs=10 2>/dev/null) test -z "$chunk" && break ...process $chunk... done


shell 操作符 ":" (冒号) 的功能是返回 0 (true,真值) 。这样 while 循环将一直运行,直到循环体中执行了 break 命令。

bash 命令替换 "$(命令)" 从 dd 的标准输出中截获 10 字节,然后保存到 shell 变量 trunk 中。我们丢弃了 dd 的标准错误,包括那些真正的错误。

接下来的代码测试 $chunk 是否为空,如果是的话就跳出循环。它不能像 2004 年 5 月那篇文章中那样,测试 dd 的退出状态,因为无论是否读取数据,dd 都返回 0。这里使用的技巧在很多情况下都有用。记住设置 count=1,使 dd 不要在一次循环中读取所有的数据。

正文结束

Jerry Peek is a freelance writer and instructor who has used Unix and Linux for over 20 years. He's happy to hear from readers; see http://www.jpeek.com/contact.html.


Linux Magazine October 2004

Copyright Linux Magazine ©2004