dd、split、csplit命令
dd、split、csplit命令
在Linux最常用的文件生成和切片工具是dd,它功能比较全面,但无法以行为单位提取文件数据,也无法直接将文件按大小或行数进行均分(除非借助循环)。另两款数据分割工具split和csplit能够比较轻松地实现这些需求。csplit是split的升级版。
在处理很大的文件时,一个非常高效的思路是将大文件切割成多个小文件片段,然后再通过多个进程/线程对各个小文件进行操作,最后合并总数居。就像sort命令,它在实现排序时,底层算法就涉及到了将一个大文件切割成多个临时小文件。
dd命令
从if指定的文件读取数据,写入到of指定的文件。使用bs指定读取和写入的块大小,使用count指定读取和写入的数据块数量,bs和count相乘就是文件总大小。可以指定skip忽略读取if指定文件的前多少个块,seek指定写入到of指定文件时忽略前多少个块。
1 | dd if=/dev/zero of=/tmp/abc.1 bs=1M count=20 |
if是input file,of是output file;bs有c(1byte)、w(2bytes)、b(512bytes)、kB(1000bytes)、K(1024bytes)、MB(1000)、M(1024)和GB、G等几种单位。因此,不要随意在单位后加上字母B。
假设现有文件CentOS.iso的大小1.3G,需要将其切分后还原,切分的第一个小文件大小为500M。
1 | dd if=/tmp/CentOS.iso of=/tmp/CentOS1.iso bs=2M count=250 |
生成第二个小文件,由于第二个小文件不知道具体大小,所以不指定count选项。由于第二个小文件要从第500M处开始切分,于是需要忽略CentOS.iso的前500M。假设bs=2M,于是skip掉的数据块数量为250。
1 | dd if=/tmp/CentOS.iso of=/tmp/CentOS2.iso bs=2M skip=250 |
现在CentOS.iso=CentOS1.iso+CentOS2.iso。可以将CentOS[1-2].iso还原。
1 | cat CentOS1.iso CentOS2.iso >CentOS_m.iso |
比较CentOS_m.iso和CentOS.iso的md5值,它们是完全一样的。
1 | $ md5sum CentOS_m.iso CentOS.iso |
那么seek选项呢?和skip有什么区别?skip选项是忽略读取时的前N个数据块,而seek是忽略写入文件的前N个数据块。假如要写入的文件为a.log,则seek=2时,将从a.log的第3个数据块开始追加数据,如果a.log文件本身大小就不足2个数据块,则缺少的部分自动使用/dev/zero填充。
于是,在有了CentOS1.iso的基础上,要将其还原为和CentOS.iso相同的文件,可以使用下面的方法:
1 | dd if=/tmp/CentOS.iso of=/tmp/CentOS1.iso bs=2M skip=250 seek=250 |
还原后,它们的md5值也是相同的。
1 | $ md5sum CentOS1.iso CentOS.iso |
split命令
split工具的功能是将文件切分为多个小文件。既然要生成多个小文件,必然要指定切分文件的单位,支持按行切分以及按文件大小切分,另外还需解决小文件命名的问题。例如,文件名前缀、后缀。如果未明确指定前缀,则默认的前缀为”x”。
以下是命令的语法说明:
1 | split [OPTION]... [INPUT [PREFIX]] |
基本用法
例如,将/etc/fstab按行切分,每5行切分一次,并指定小文件的前缀为”fs_”,后缀为数值后缀,且后缀长度为2。
1 | split -l 5 -d -a 2 /etc/fstab fs_ |
查看任一小文件。
1 | cat fs_01 |
可以将这些切分后的小文件重新组装还原。例如,将上面的三个小文件还原为~/fstab.bak。
1 | cat fs_0[0-2] >~/fstab.bak |
还原后,它们的内容是完全一致的。可以使用md5sum比较。
1 | md5sum /etc/fstab ~/fstab.bak |
还可以将标准输入的数据进行切分,并分别写入到小文件中。例如:
1 | seq 1 2 15 | split -l 3 -d - new_ |
可以为每个小文件追加额外的后缀。有些老版本的split不支持该选项,而是在csplit上支持的,但是新版本的split已经支持。例如,加上”.log”。
1 | seq 1 2 20 | split -l 3 -d -a 3 --additional-suffix=".log" - new1_ |
按CHUNKS进行分割
split的”-n”选项是按照CHUNK的方式进行文件切割:
1 | '-e' 当使用-n时,不要生成空文件。例如5行数据,却要求按行切割成100个文件,显然第5个文件之后都是空文件 |
可能不是很好理解,给几个示例就很清晰了。
假如文件1.txt中有a-z,每个字母占一行,共26行。将这个文件进行切割:
1.指定CHUNK=N或K/N时
将1.txt均分为5个文件。由于1.txt共52字节(每行中,字母一个字节,换行符一个字节),所以分割后每个文件10字节,最后一个文件12字节。
1 | split -n 5 1.txt fs_ |
如果再指定K/N的K,例如指定为2,则输出属于fs_ab文件中的内容:
1 | split -n 2/5 1.txt fs_ |
2.指定CHUNK=l/N或l/K/N时
这时将按照行的总数量进行均分。例如,将1.txt中的26行分割成5个文件,前4个文件将各有5行,第5个文件将有6行:
1 | split -n l/5 1.txt fs_ |
如果指定K,则输出属于第K个文件的内容:
1 | split -n l/2/5 1.txt fs_ |
3.指定CHUNK=r/N或r/K/N时
使用r时,将每切割一行就轮询到下一个文件,轮完所有文件再返回来切割到第一个文件。
直接看结果:
1 | split -n r/5 1.txt fs_ |
a输出到第1个文件,b输出到第2个文件,c输出到第3个文件,依此类推。
指定K时,将输出第K个文件的内容:
1 | split -n r/2/5 1.txt fs_ |
filter CMD将切割结果传递给指定命令
默认情况下split是将文件切割传递到各文件片段中,如果使用–filter选项,则不再切割到文件片段中,而是通过管道的形式传递给CMD进行处理。CMD处理时,有可能并不需要将数据存储到文件中,但如果需要将处理后的数据再分片段保存到多个文件中,则可以使用$FILE来表示(这是split识别的变量,要避免被shell解析),就像split的普通切割模式一样。
例如,读取1.txt中的26行,每5行管道传递一次,然后使用echo将它们输出:
1 | split -n l/5 --filter='xargs -i echo ---:{}' 1.txt |
这时split是没有生成任何小文件片段的。如果想要将上面的输出保存到小文件片段中,使用$FILE,这个变量是split内置变量,不能被shell解析,所以出现$split的地方必须使用单引号保护起来:
1 | split -n l/5 --filter='xargs -i echo ---:{} >$FILE.log' 1.txt fs_ |
其中的$FILE就是split进行命名的部分。上面的小文件中,都是以fs_为前缀,以”.log”为后缀。
filter有时候是很有用的,例如将一个大的压缩文件,切割成多个小的压缩文件。
1 | xz -dc BIG.xz | split -b200G --filter='xz > $FILE.xz' - big- |
xz -dc
表示解压到标准输出,解压的数据流将传递给split,然后每200G就通过filter中的xz命令进行压缩,压缩后的文件名格式类似于big-aa.xz big-ab.xz
。
csplit命令
split只能按行或按照大小进行切分,无法按段落切分。csplit是split的变体,功能更多,它主要是按指定上下文按段落分割文件。
1 | csplit [OPTION]... FILE PATTERN... |
假设文件内容如下:
1 | cat test.txt |
假设每个SERVER-n表示一个段落,于是要按照段落切分该文件,使用以下语句:
1 | csplit -f test_ -b %04d.log test.txt /SERVER/ {*} |
“-f test_”指定小文件前缀为”test_”,-b %04d.log
指定文件后缀格式”00xx.log”,它自动为每个小文件追加额外的后缀”.log”,”/SERVER/“表示匹配的模式,每匹配到一次,就生成一个小文件,且匹配到的行是该小文件中的内容,{*}
表示无限匹配前一个模式即/SERVER/直到文件结尾,假如不知道{*}
或指定为{1}
,将匹配一次成功后就不再匹配。
1 | ls test_* |
上面的文件中虽然只有三个段落:SERVER-1,SERVER-2,SERVER-3,但切分的结果生成了4个小文件,并且注意到第一个小文件大小为0字节。为什么会如此?因为在模式匹配的时候,每匹配到一行,这一行就作为下一个小文件的起始行。由于此文件第一行”SERVER-1”就被/SERVER/匹配到了,因此这一行是作为下一个小文件的内容,在此小文件之前还自动生成一个空文件。
生成的空文件可以使用”-z”选项来删除。
1 | csplit -f test1_ -z -b %04d.log test.txt /SERVER/ {*} |
还可以指定只拷贝匹配到的行偏移数量。例如,匹配到行时,只拷贝它后面的1行(包括它自身共两行),但多余的行将放入下一个小文件中。
1 | csplit -f test2_ -z -b %04d.log test.txt /SERVER/+2 {*} |
第一个小文件只有两行。
1 | cat test2_0000.log |
SERVER-1段落的其余内容放入到了第二个小文件中。
1 | cat test2_0001.log |
同理第三个小文件也一样,直到最后一个小文件中存放剩余所有无法匹配的内容。
1 | cat test2_0003.log |
指定”-s”或”-q”选项以静默模式运行,将不会输出小文件的大小信息。
1 | csplit -q -f test3_ -z -b %04d.log test.txt /SERVER/+2 {*} |