背景
想象一个场景,假如有一个导出的数据库纯文本文件需要更改其中一两行的内容,有什么快速有效的办法?
服务器配置好的情况下:
- 当文本内容是十几G的时候,我们可以考虑使用 vim 来修改,无非是定位行,搜索关键字,更改后保存慢一些
- 当文本内容是几十G的时候,我们可以考虑使用 sed 等命令搜索更改文本内容,这时候保存是非常慢的,vim几乎处理不了了,并且这些命令会全量的生成临时文件,需要两倍的文件空间,最后替换才能保存成功
- 那么就没有好办法,能够直接修改文件的部分内容,而不需要这么长时间吗,就算仅修改文件起始行的某个字符这样的简单需求
方案
反向推理,我们需要一个手段能截断文件的内容,让需要修改的部分不要那么大,在修改完成后再合并这些内容即可
方案1
最开始我想到的是 split,它能够切割文件,并且它切割的速度是非常快的,假如有一个 270G 的文件需要修改,那么我可以切割成每个文件为 40G 甚至 20G ,在某个文件中修改完指定内容后合并所有文件即可。这里面花费的时间主要有几个:
- 按照每个子文件大小为 20G 切割一个 270G 的文件 (这部分也比较慢,raidz3机械400M左右读写需一个小时)
- 找到需要修改的内容,如果内容比较靠前或者靠后那么基本是第一或者最后一个文件
- 修改文件,文件只有20G了,那么修改的速度是可以接收的
- 合并文件 (这部分的时间也比较慢,大概670秒)
方案2
上面方案中切割文件虽然从理论上比直接修改原文件更好,比如不需要生成全量临时文件,修改的时候只改一部分,时间和操作可控(相对于直接修改270G文件时间是未知数)。但它仍然是有很大的缺陷 它仍然需要修改数十G的文件,并且切割和合并慢,总之就是需要操作的还是文件太大 那么有没有方法能够把文件的一部分切割出来,修改之后再替换呢? 这样似乎就规避了所有的缺点,答案是有的,我们可以使用 dd 来实现。大致的思路如下:
- 首先我们把文件需要修改的一部分切割下来,这里可以使用
dd
orhead
ortail
,这部分的重点是 最好能够截断到某一完整的行,所以多几行也是没关系的,原因后面会介绍,然后记录文件的字符数wc -c
- 接下来修改截断的这部分内容,就算截取了几万行,几十万行,甚至上百万行,这个速度仍然是很快的 重点: 修改之前查看文本最后是否存在换行符,如果没有 vim 需要设置 binary, noendofline 这样vim不会自动添加,否则vim会自动添加一个换行符,导致文件格式出现问题
- 接下来调整文件的字节数,保证修改后的文件与截断之前的文件字节数一致,使用
wc -c
确认,如果长度变了,覆盖文本的时候会有问题,举例:原文本是26个字母,然后截取前8个修改内容,但是修改后长度变成了16,那么会覆盖这16个字母到源26个字母中,中间不想被修改的部分也被覆盖了,所以这里重点注意 - 在覆盖文件后,查看处理后的文件,有两个地方要检查,分别是:修改的地方是否成功,覆盖的末尾处是否存在截断或者内容不匹配等
实际操作:
- 使用
dd
orhead
ortail
取文件需要修改的附近处,dd
设置bs=1
大概率不是完整的行,需要设置vim binary, noendofline
, 而head, tail
一般都是完整的行,可以不需要设置 - 查看文件的字符数,
wc -c
修改文件内容,使用你喜欢的方式即可 - 查看新字符数是否跟之前一致,少了则补,多了则删
- 覆盖新文本到原文本
dd if=temp.txt of=access.txt conv=notrunc
,最重要的是后面这个参数,一般使用 dd 会把文件覆盖成新文本,就是新文本内容比原文本短的话,多余的会被截断,相当于只会保留新文本,使用这个参数后会保持原文本后面的内容不动
评论