用于字符串替换的非面向行工具?

用于字符串替换的非面向行工具?

我最近问一个问题关于如何删除出现在另一个特定字符之后的换行符。

Unix 文本处理工具非常强大,但几乎所有工具都处理文本行,当输入适合可用内存时,这在大多数情况下都很好。

但是,如果我想替换一个不包含任何换行符的大文件中的文本序列,我该怎么办?

例如替换<foobar>\n<foobar>而不逐行读取输入? (因为只有一行并且有 2.5G 个字符长)。

答案1

面对此类问题时,我首先想到的是更改记录分隔符。在大多数工具中,这是\n默认设置的,但可以更改。例如:

  1. 珀尔

    perl -0x3E -pe 's/<foobar>/\n$&/' file
    

    解释

    • -0:这将输入记录分隔符设置为给定的字符十六进制值。在本例中,我将其设置为>十六进制值为3E。一般格式为-0xHEX_VALUE.这只是将线路分成可管理块的技巧。
    • -pe:应用 给出的脚本后打印每个输入行-e
    • s/<foobar>/\n$&/: 简单的替换。$&在本例中, 是匹配的内容<foobar>
  2. awk

    awk '{gsub(/foobar>/,"\n<foobar>");printf "%s",$0};' RS="<" file
    

    解释

    • 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

awkperl使用的内存量都可以忽略不计。

答案2

格萨尔 (一般搜索和替换)正是用于此目的的一个非常有用的工具。

这个问题的大多数答案都使用基于记录的工具和各种技巧来使它们适应问题,例如将默认的记录分隔符切换为假设在输入中经常出现的字符,以免每个记录太大而无法处理。

在许多情况下,这是非常好的,甚至是可读的。我确实喜欢可以使用随处可用的工具(例如 、 和 bourne shell)轻松/有效地解决awktr问题sed

在具有随机内容的任意大文件中执行二进制搜索和替换不太适合这些标准 UNIX 工具。

你们中的一些人可能认为这是作弊,但我不认为使用正确的工具来完成工作怎么可能是错误的。在本例中,它是一个名为的 C 程序,gsar其许可权为通用公共许可证 v2,所以让我感到非常惊讶的是,两个版本中都没有这个非常有用的工具的软件包巴布亚新几内亚,红帽,也不乌班图

gsar使用二进制变体Boyer-Moore 字符串搜索算法

用法很简单:

gsar -F '-s<foobar>' '-r:x0A<foobar>'

其中-F表示“过滤”模式,即stdin读写stdout。也有对文件进行操作的方法。-s指定搜索字符串和-r替换字符串。冒号表示法可用于指定任意字节值。

支持不区分大小写的模式 ( -i),但不支持正则表达式,因为该算法使用搜索字符串的长度来优化搜索。

该工具也可以仅用于搜索,有点像grep.gsar -b输出匹配的搜索字符串的字节偏移量,并gsar -l打印文件名和匹配数(如果有),有点像grep -lwc.

该工具的编写者是托莫德·查伯格(初始)和汉斯·彼得·凡尔纳(改进)。

答案3

在目标字符串和替换字符串长度相同的狭窄情况下,内存映射可以来救援。如果需要就地进行更换,这尤其有用。您基本上是将文件映射到进程的虚拟内存中,并且 64 位寻址的地址空间非常大。请注意,文件不一定会一次全部映射到物理内存中,因此可以处理数倍于机器上可用物理内存大小的文件。

这是一个 Python 示例,替换foobarXXXXXX

#! /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)

答案4

awk 对连续的记录进行操作。它可以使用任何字符作为记录分隔符(许多实现中的空字节除外)。某些实现支持任意正则表达式(不匹配空字符串)作为记录分隔符,但这可能很麻烦,因为记录分隔符在被存放之前从每个记录的末尾被截断$0(GNU awk 将变量设置RT为记录分隔符)已从当前记录的末尾删除)。请注意,它print以输出记录分隔符终止其输出,ORS该分隔符默认为换行符,并且独立于输入记录分隔符进行设置RS

awk -v RS=, 'NR==1 {printf "input up to the first comma: %s\n", $0}'

您可以通过用 交换换行符来有效地选择不同的字符作为其他工具的记录分隔符(sort、、 ...) 。sedtr

tr '\n,' ',\n' |
sed 's/foo/bar/' |
sort |
tr '\n,' ',\n'

许多 GNU 文本实用程序支持使用空字节而不是换行符作为分隔符。

相关内容