我刚刚遇到一个问题,必须从一个大(千兆字节)大小的文件中剪切一些行,并且意识到潜在的 CPU 占用试图在内存中读取它,我想就地编辑它......所以遇到了这些问题:
...还有这些:
然而,我在思考其他事情:我相信(但我不确定)任何文件系统(例如ext3
)都必须使用类似链接列表的东西,以便能够描述类似文件片段之类的东西映射到磁盘区域。
因此,应该可以做这样的事情 - 例如,假设我有一个bigfile.dat
这样的文件(数字应该表示字节偏移量,但对齐它们有点困难):
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
L 1\n L 2\n L 3\n L 4\n L 5\n L 6\n
原则上,该文件可以加载到终端应用程序中进行浏览 - 假设我们调用一个 tool editsegments bigfile.dat
,并且假设它类似于less -N bigfile.dat
显示相同文件(带有行号)的方式:
1 1 L 1
2 2 L 2 *
3 3 L 3
4 4 L 4 *
5 5 L 5
6 6 L 6
bigfile.dat (END)
比方说,我可以在那里输入一个命令(比如d
删除行),单击另一个键或鼠标,上面显示*
- 意味着第 2 行和第 4 行之间的所有内容都应该被删除。然后程序将做出响应并显示以下内容:
1 1 L 1
2 5 L 5
3 6 L 6
bigfile.dat (END)
现在我们可以看到最左边的第一列显示“新”行号(剪切后),第二列是“旧”行号(剪切前) - 然后是实际的行内容。
现在,我想象在这个伪应用程序editsegments
退出后会发生什么,首先也是最重要的是,bigfile.dat
它没有受到影响;但是,现在同一目录中还会有一个额外的文本文件,例如bigfile.dat.segments
;包含这些内容:
d 4:15 # line 2-4
bigfile.dat.iedit
...此外,还会出现一个特殊文件(如“符号链接”)——我们称之为“它”。
现在,基本上,所有这一切的结果将是,如果我现在尝试bigfile.dat.iedit
使用类似的东西打开less -N bigfile.dat.iedit
,我想获得“编辑”的内容:
1 L 1
2 L 5
3 L 6
bigfile.dat (END)
...我想,这可以通过以某种方式指示操作系统来实现,即当$FILE.iedit
打开时,首先$FILE.segments
应该打开并读取;将d 4:15
指示原始文件中的字节 4 到 15 应被省略 - 这将导致类似以下内容的结果:
0 1 2 3 4 5 6 7 8 9 10 11 12,3,4 15 16 17 18 19 20 21 22 23
L 1\n
L
2
\n
L
3
\n
L
4
\n
L 5\n
L 6\n
0 1 2 3 ------------------------------------------>16 17 18 19 20 21 22 23
换句话说 -假设在文件的文件系统概念中,内容的每个字节还包含到链中下一个字节的“链接” - 应该可以指示文件系统基于脚本建立新的链表,并提供内容如通过特殊文件(符号链接或管道)修改后的链表所表示的。
这就是我在标题中所说的“脚本化”的意思——“新”链表可以由脚本文件($FILE.segments
)控制,用户可以在文本编辑器中编辑(或由前端应用程序生成)。我所说的“多次”是指bigfile.dat
在这个过程中根本没有修改;所以我今天可以编辑第一个(原始)千兆字节,将进度保存在 ( $FILE.segments
) 中 - 然后我可以明天编辑第二个千兆字节,再次将进度保存在 ( $FILE.segments
) 中,等等 - 一直以来,原始内容bigfile.dat
都没有改变。
当所有编辑完成后,人们可能会调用某种命令(例如,editsegments --finalize bigfile.dat
),它将简单地将新链接列表永久编码为 的内容bigfile.dat
(并与此一致,删除bigfile.dat.segments
和bigfile.dat.iedit
)。或者更简单,人们可以这样做:
cp bigfile.dat.iedit /path/to/somewhere/else/bigfile.modified.dat
当然,除了d
elete 脚本命令之外,r
还可以有 eplace 命令,例如:
r 16:18 AAA
...说:将字节 16 和 18 之间的内容替换为空格后的下一个 18-16+1=3 个字节(即AAA
) - 链表实际上可以“挂钩”到脚本命令内容本身(下图还包含d
elete):
0 1 2 3 4 5 6 7 8 9 10 11 12,3,4 15 16 17 18 19 20 21 22 23
L 1\n
L
2
\n
L
3
\n
L
4
\n
L
5
\n
L 6\n
0 1 2 3 ------------------------------------------>| | 19 20 21 22 23
.
.
.
.
.
.
.
\n
r
1
6
:
1
8
AAA
\n
.
.
.
.
现在,我想程序就像hexedit
(如上所述这里)确实就地更改文件 - 但我只是喜欢脚本编写可能性的好处(如果它可以由 GUI 应用程序管理,即使是终端应用程序,那就更好了),以及实际上没有原始文件的好处更改,直到确认所有编辑均符合要求。
我不确定这样的事情是否可能 - 即使是,我想它可能需要一个专用的驱动程序(而不仅仅是一个用户程序)......但我想无论如何还是值得一问 - 是否有Linux 上有类似的东西吗?
非常感谢您的回答,
干杯!
答案1
磁盘上文件的结构取决于所使用的文件系统。现实世界的文件系统都不使用您所描述的链接列表(这会让人fseek(3)
难以忍受)。与此最接近的是微软的胖的,本质上是将指针从数据块移到一个数组中来隐藏它们。
但大多数文件系统确实使用一些对文件中数据块的基于指针的引用,因此原则上,只需打乱少量指针(而不是整个文件内容)并在文件中标记一个块,就可以剪切出文件的一个块。文件的中间部分是空闲的。遗憾的是,这不是一个非常有用的操作,文件块相当大(通常为 4KiB),并且很少与文件中的结构(无论是行还是其他细分)合理对齐。
答案2
你所描述的听起来很像重播一个文本编辑器的重做列表针对未更改的原始文件重做列表属于.我很确定gvim
有这样一个坚持不懈的撤消/重做列表,您可能(?)能够利用它,而且我知道emacs
肯定有这样一个列表,您很可能可以哄骗它做任何您想做的事情(通过脚本elisp
),例如。保存会话之间的 Emacs 撤消历史记录。
顺便说一句,对于如此大的文件,关闭所有不需要的操作可能是一个好主意,例如:自动保存,语法高亮显示(慢上大的emacs 文件)等,32 位系统上的 emacs 有 256 MB文件大小限制。
它当然不会像您所建议的那样简洁,但如果没有大量更改,则可能有用。
答案3
通常,如果不将整个文件放入内存,则无法就地编辑文件。我假设您真正想要做的只是拥有一个新文件,它是旧文件的副本,没有特定的行。这可以使用 unix 实用程序head
和轻松完成tail
。例如,要从文件中复制除第 5、12 和 52 行之外的所有内容,您可以执行以下操作
head -n 4 bigfile.dat > tempfile.dat
tail -n +6 bigfile.dat | head -n 6 >> tempfile.dat
tail -n +13 bigfile.dat | head -n 39 >> tempfile.dat
tail -n 53 bigfile.dat >> tempfile.dat
如果您不熟悉这些实用程序,我将更详细地解释。
该head
实用程序打印出文件中的前 n 行。如果没有给出位置参数,它将使用标准输入作为文件。该-n
标志告诉 head 要打印多少行。因此,head -n 2
将仅打印标准输入的前两行。
该tail
实用程序打印出文件的最后 n 行。与 head 一样,它可以从文件或标准输入中读取。 -n 标志告诉 tail 从末尾开始打印多少行。您还可以在数字前面加上加号,以告诉 tail 从文件末尾开始打印从开头开始的那么多行。例如,tail -n 2
打印标准输入的最后两行。但是tail -n +2
打印出从第 2 行开始的所有行(省略第 1 行)。
所以一般来说,如果你想从文件中打印 [x, y) 范围内的行,你会这样做
`tail -n +x | head -n d`
其中 d = y - x。这些命令将生成一个新文件。如果您愿意,您可以删除旧文件。这样做的好处是,每次只需要在内存中保留一行,因此它不会很快填满您的 RAM head
。tail
答案4
听起来像是 sed 脚本的工作。 IIRC 它是为此类任务而设计的。逐行处理、同一组命令的重复处理以及正则表达式都合并在一个工具中。虽然我知道它会完成这项工作,但除了指导您完成任务之外,我无法进一步指导您手册页。