在大文件的开头和结尾添加行


23

我有在大文件的开头和结尾添加行的情况。

我已经尝试过如下图所示。

  • 第一行:

    sed -i '1i\'"$FirstLine" $Filename
  • 最后一行:

    sed -i '$ a\'"$Lastline" $Filename  

但是此命令的问题在于它附加了文件的第一行并遍历整个文件。对于最后一行,它再次遍历整个文件并追加最后一行。由于文件非常大(14GB),因此需要很长时间。

如何在仅读取一次文件的同时在文件的开头添加一行,并在文件的末尾添加另一行?

Answers:


20

sed -i使用临时文件作为实现细节,这就是您正在经历的;但是,在不覆盖现有内容的情况下将数据放在数据流的开头需要重写文件,即使避免了,也无法解决sed -i

如果无法重写文件,则可以在读取文件时考虑对其进行处理,例如:

{ echo some prepended text ; cat file ; } | command

此外,sed用于编辑流-文件不是流。使用为此目的而设计的程序,例如ed或ex。-ised 的选项不仅不可移植,而且还会断开与文件的任何符号链接,因为它实际上会删除并重新创建它,这毫无意义。

您可以在单个命令中执行以下操作ed

ed -s file << 'EOF'
0a
prepend these lines
to the beginning
.
$a
append these lines
to the end
.
w
EOF

请注意,根据ed的实现,它可能会使用分页文件,要求您至少有那么多可用空间。


嗨,您提供的ed命令对于大型文件非常有效。但是我有3个巨大的文件,例如Test,Test1,Test2。我给了像ed -s Tes * <<'EOF'0a这样的命令,这些行都放在开头。$ a将这些行添加到末尾。w EOF但它仅接受测试文件并添加第一行/最后一行。我们如何在同一命令中进行更改,以便它必须在所有文件中添加第一行和最后一行。
UNIXbest13年

@UNIXbest-使用for循环:for file in Tes*; do [command]; done
克里斯·

嗨,我在Tes *中使用了以下命令来存储文件;做ed -s Tes * <<'EOF'0a HEllO HDR。$ a Hello TLR。w EOF完成,但仍写入第一个文件。
UNIXbest13年

正确,因为您需要使用"$file",而不是Tes*的参数ed
克里斯·唐

2
@UNIXbest如果此答案已解决您的问题,则应考虑接受它。
约瑟夫R.13年

9

请注意,如果要避免在磁盘上分配文件的整个副本,可以执行以下操作:

sed '
1i\
begin
$a\
end' < file 1<> file

这利用了以下事实:当其stdin / stdout是文件时,sed 将按块进行读取和写入。因此,只要您添加的第一行小于sed的块大小(应为4k或8k之类),就可以覆盖正在读取的文件。

请注意,尽管如果由于某种原因sed失败(被杀死,机器崩溃……),您将最终处理一半的文件,这意味着某些数据的中间部分缺少第一行的大小。

还要注意,除非您sed是GNU sed,否则它将不适用于二进制数据(但是由于您使用-i,因此您正在使用GNU sed)。


在Ubuntu 16.04上对我来说是这个错误
Csaba Toth 16'Nov

4

以下是一些选择(所有选择都会创建文件的新副本,因此请确保您有足够的空间用于该文件):

  • 简单的回声/猫

    echo "first" > new_file; cat $File >> new_file; \
      echo "last" >> new_file; 
  • awk / gawk等

    gawk 'BEGIN{print "first\n"}{print}END{print "last\n"}' $File > NewFile 

    awk并逐行读取文件。该BEGIN{}块在第一行之前执行,而END{}块在最后一行之后执行。因此,以上命令表示print "first" at the beginning, then print every line in the file and print "last" at the end

  • 佩尔

    perl -ne 'BEGIN{print "first\n"} print;END{print "last\n"}' $File > NewFile

    这与上面用Perl编写的gawk本质上是相同的。


1
请注意,在所有这些情况下,新文件至少需要至少14GB的空间。
克里斯·

@ChrisDown好点了,我编辑了答案以使其清楚。我认为这不是问题,因为OP正在使用sed -i它来创建临时文件。
terdon

3

我喜欢简单得多的方法:

gsed -i '1s/^/foo\n/gm; $s/$/\nbar/gm' filename.txt

这将转换文件:

asdf
qwer

到文件:

foo
asdf
qwer
bar

2

您可以在Ex模式下使用Vim:

ex -sc '1i|ALFA' -c '$a|BRAVO' -cx file
  1. 1 选择第一行

  2. i 插入文字和换行符

  3. $ 选择最后一行

  4. a 附加文字和换行符

  5. x 保存并关闭


如果我们想对多个文件执行该怎么办?
geoyws

1
@geoyws不在此问题范围内
Steven Penny

您确定是$ a而不是%a吗?
卡洛斯·罗伯斯

2

无法在文件的开头¹插入数据,您所能做的就是创建一个新文件,写入其他数据并附加旧数据。因此,您必须至少重写一次整个文件才能插入第一行。但是,您可以追加最后一行而不重写文件。

sed -i '1i\'"$FirstLine" $Filename
echo "$LastLine" >>$Filename

或者,您可以在一次sed中组合两个命令。

sed -i -e '1i\'"$FirstLine" -e '$ a\'"$Lastline" $Filename

sed -i创建一个新的输出文件,然后将其移到旧文件上。这意味着,在sed工作时,存在占用空间的文件的第二个副本。您可以通过在适当的位置覆盖文件来避免这种情况,但是有一些主要限制:要添加的行必须小于sed的缓冲区,并且如果系统崩溃,最终将导致文件损坏并且文件中的某些内容丢失中间,因此强烈建议您反对。

¹Linux 确实可以将数据插入文件,但是它只能插入整数个文件系统块,不能插入任意长度的字符串。它对某些应用程序(例如数据库和虚拟机)很有用,但对文本文件却没有用。


不对。看一下现代内核(4.xx)中XFS和ext4 fallocate()上的FALLOC_FL_INSERT_RANGE可用信息man7.org/linux/man-pages/man2/fallocate.2.html
Eric

@Eric但是,您只能插入整个块,但不能插入任意字节长度,至少从Linux 4.15.0 ext4开始。有没有可以插入任意字节长度的文件系统?
吉尔斯(Gilles)“所以

是的,但这仍然不能使您的陈述正确。您写道:“没有办法在文件的开头插入数据”。事实并非如此:有一种机制可以在文件的开头插入扩展区。当然,它带有警告,但值得一提,因为某些用户可能不关心通过填充空格或回车符来限制块大小。
埃里克


-1

现代Linux内核(高于4.1或4.2)支持通过ext4和xfs文件系统上的fallocate()系统调用FALLOC_FL_INSERT_RANGE在文件的开头插入数据。从本质上讲,这是一个逻辑移位操作:数据在逻辑上以较高的偏移量重定位。

关于要在文件开头插入的范围的粒度存在限制。但是对于文本文件,您可能可以分配比要求更多的空间(直到粒度边界)并用空格或回车符填充,但这取决于您的应用程序

我不知道有任何可操纵文件范围的现成的Linux实用程序,但编写起来并不难:获取文件描述符并fallocate()使用适当的参数进行调用。有关更多详细信息,请参见fallocate系统调用的手册页:http : //man7.org/linux/man-pages/man2/fallocate.2.html


实用程序不是问题(假设是非嵌入式Linux):util-linux包含一个fallocate实用程序。问题是整个块的粒度使大多数文本文件无法使用。另一个问题是范围分配和后续修改不是原子的。因此,这实际上并不能解决问题。
吉尔斯(Gilles)“所以

粒度是我已经提到的警告,不,它不会使它无用,它取决于应用程序。您在哪里看到原子性很重要?我只能看到表演的问题。即使这样,这个系统调用似乎也是原子的:elixir.bootlin.com/linux/latest/source/fs/open.c#L228,如果原子性变得很重要(不是,但是为了论证而说)只需使用文件锁定。(请指出内核代码中fallocate原子性被破坏的位置,我很好奇)
Eric
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.