我最近问了一个问题,如果换行符出现在另一个特定字符之后,如何删除它。
Unix 文本处理工具非常强大,但几乎所有这些工具都处理文本行,当输入适合可用内存时,这在大多数情况下都很好。
但是如果我想替换一个不包含任何换行符的大文件中的文本序列,我该怎么办?
例如<foobar>
,在\n<foobar>
不逐行读取输入的情况下替换为?(因为只有一行,而且长度为 2.5G 个字符)。
ter*_*don 12
遇到此类问题时,我首先想到的是更改记录分隔符。在大多数工具中,这是\n
默认设置的,但可以更改。例如:
珀尔
perl -0x3E -pe 's/<foobar>/\n$&/' file
Run Code Online (Sandbox Code Playgroud)
-0
: 这将输入记录分隔符设置为给定其十六进制值的字符。在这种情况下,我将>
其设置为十六进制值为3E
. 一般格式为-0xHEX_VALUE
. 这只是将线路分成可管理的块的技巧。-pe
: 在应用给定的脚本后打印每个输入行-e
。s/<foobar>/\n$&/
: 一个简单的替换。该$&
是什么是匹配的,在这种情况下<foobar>
。awk
awk '{gsub(/foobar>/,"\n<foobar>");printf "%s",$0};' RS="<" file
Run Code Online (Sandbox Code Playgroud)
RS="<"
: 将输入记录分隔符设置为>
.gsub(/foobar>/,"\n<foobar>")
:替换的所有情况foobar>
有\n<foobar>
。请注意,由于RS
已设置为<
,因此所有内容<
都从输入文件中删除(这就是awk
工作方式),因此我们需要匹配foobar>
(没有<
)并替换为\n<foobar>
。printf "%s",$0
: 在替换后打印当前的“行”。$0
是当前记录,awk
因此它将保留<
.我在使用以下命令创建的 2.3 GB 单行文件上测试了这些:
for i in {1..900000}; do printf "blah blah <foobar>blah blah"; done > file
for i in {1..100}; do cat file >> file1; done
mv file1 file
Run Code Online (Sandbox Code Playgroud)
awk
和perl
使用的内存量都可以忽略不计。
gsar (通用搜索和替换)正是用于此目的的非常有用的工具。
这个问题的大多数答案都使用基于记录的工具和各种技巧来使它们适应问题,例如将默认的记录分隔符切换到假设在输入中出现的频率足够高的字符,以免每个记录过大而无法处理。
在许多情况下,这非常好,甚至可读。我不喜欢的问题,可以很容易地/一个到处可用的工具,如有效的解决awk
,tr
,sed
和Bourne shell。
在具有随机内容的任意大文件中执行二进制搜索并替换这些标准 unix 工具不太适合。
你们中的一些人可能认为这是作弊,但我不认为使用正确的工具来完成工作是错误的。在这种情况下,它是一个 C 程序gsar
,它在GPL v2下获得许可,因此令我感到惊讶的是,无论gentoo、redhat还是ubuntu都没有这个非常有用的工具的软件包。
gsar
使用Boyer-Moore 字符串搜索算法的二进制变体。
用法很简单:
gsar -F '-s<foobar>' '-r:x0A<foobar>'
Run Code Online (Sandbox Code Playgroud)
其中-F
表示“过滤器”模式,即stdin
读写stdout
。还有一些方法可以对文件进行操作。-s
指定搜索字符串和-r
替换。冒号符号可用于指定任意字节值。
支持不区分大小写模式 ( -i
),但不支持正则表达式,因为该算法使用搜索字符串的长度来优化搜索。
该工具也可以仅用于搜索,有点像grep
. gsar -b
输出匹配搜索字符串的字节偏移量,并gsar -l
打印文件名和匹配数(如果有),有点像grep -l
与wc
.
该工具由Tormod Tjaberg(初始)和Hans Peter Verne(改进)编写。
在目标字符串和替换字符串长度相同的狭窄情况下,内存映射可以派上用场。如果需要就地进行更换,这尤其有用。您基本上是将文件映射到进程的虚拟内存中,而 64 位寻址的地址空间是巨大的。请注意,文件不一定一次全部映射到物理内存中,因此可以处理数倍于机器上可用物理内存大小的文件。
这里有一个Python的例子,取代foobar
与XXXXXX
#! /usr/bin/python
import mmap
import contextlib
with open('test.file', 'r+') as f:
with contextlib.closing(mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_WRITE)) as m:
pos = 0
pos = m.find('foobar', pos)
while pos > 0:
m[pos: pos+len('XXXXXX')] = 'XXXXXX'
pos = m.find('foobar', pos)
Run Code Online (Sandbox Code Playgroud)