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


13

我最近问了一个问题,如果换行符出现在另一个特定字符之后,该如何删除。

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

但是,如果我希望在不包含换行符的巨大文件中替换文本序列,该怎么办?

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


1
您愿意使用perlpython吗?
iruvar 2014年

Perl很好。我刚刚找到了gsarhome.online.no/~tjaberg)我会尝试的。
MattBianco 2014年

Answers:


12

面对此类问题时,我发生的第一件事是更改记录分隔符。在大多数工具中,\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使用的内存量都可以忽略不计。


您是否尝试过Tie::File perldoc.perl.org/Tie/File.html。我认为这是Perl处理大型文件时的最佳功能。
cuonglm 2014年

@Gnouc我已经玩了一点,是的。但是我)OP已经在另一个问题上表示不喜欢Perl,所以我想保持简单。ii)除非绝对必要,否则我倾向于避免使用外部模块; iii)使用Tie :: File模块会使语法大大减少。明确。
terdon

同意。注意一点,这Tie::File是自以来的核心模块v5.7.3
cuonglm 2014年

9

gsar (一般搜索和替换)是用于此目的的非常有用的工具。

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

在许多情况下,这非常好,甚至可读。我不喜欢的问题,可以很容易地/一个到处可用的工具,如有效的解决awktrsed和Bourne shell。

对于这些标准的UNIX工具,执行二进制搜索并替换为具有随机内容的任意大文件并不十分合适。

你们中的有些人可能认为这是作弊,但我看不出使用正确的工具完成工作会是错误的。在这种情况下,这是一个gsar根据GPL v2授权的名为C的C程序,因此令我惊讶的是,gentooredhatubuntu中都没有针对该非常有用的工具的软件包。

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

用法很简单:

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

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

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

该工具也可以仅用于搜索,类似于grepgsar -b输出匹配搜索字符串的字节偏移,和gsar -l打印文件名和如果有的话,有点像组合匹配的数目grep -lwc

该工具由Tormod Tjaberg(初始)和Hans Peter Verne(改进)编写。


如果是GPL,您会考虑打包发行:)
Rqomey 2014年

1
实际上,我正在认真考虑为它制作一个gentoo ebuild。也许还有转速。但是我以前从未构建过.deb程序包,因此我希望有人击败我(因为这将需要我一些时间)。
MattBianco 2014年

我怀疑这有多安慰,但是OS X的自制软件具有的公式gsar
crazysim

5

在目标字符串和替换字符串长度相同的狭窄情况下,可以使用内存映射。如果需要就地执行更换,这将特别有用。您基本上是在将文件映射到进程的虚拟内存中,并且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

有许多用于此的工具:

dd是要阻止文件时要使用的内容-仅在一定次数下可靠地读取一定数量的字节。它可移植地处理阻塞和解除阻塞的文件流:

tr -dc '[:graph:]' </dev/urandom | dd bs=32 count=1 cbs=8 conv=unblock,sync 2>/dev/null

###OUTPUT###

UI(#Q5\e BKX2?A:Z RAxGm:qv t!;/v!)N

我也在tr上面使用它,因为它可以处理将任何ASCII字节转换为任何其他字节(或者在这种情况下,删除不是不可空间打印字符的任何ASCII字节)。实际上,这就是我今天早上回答您另一个问题时使用的方法:

tr '>\n' '\n>' | sed 's/^>*//' | tr '\n>' '>\n' 

许多相似之处。该列表应提供您可能会熟悉的最低公分母子集。

但是,如果要对2.5gbs的二进制文件进行文本处理,则可以从开始od。它可以为您提供一种octal dump或多种其他格式。您可以指定所有类型的选项-但我仅以\C转义格式每行一个字节:

您将从中获取的数据od将以您指定的任意间隔定期显示-如下所示。但是首先-这是您问题的答案:

printf 'first\nnewline\ttab spacefoobar\0null' |
od -A n -t c -v -w1 |
sed 's/^ \{1,3\}//;s/\\$/&&/;/ /bd
     /\\[0nt]/!{H;$!d};{:d
    x;s/\n//g}'

上面的那一点在\newlines,\0null,\tabs 上定界,<spaces>同时\C为定界符保留了转义的字符串。请注意所使用的Hx函数-每次sed遇到定界符时,它将交换出其内存缓冲区的内容。这样,sed仅保留了为可靠地定界文件所需的尽可能多的信息,并且不会屈服于缓冲区超限-就是说,只要它实际上遇到定界符,就不会。只要有,sed它将继续处理其输入od并将继续提供它,直到遇到它为止EOF

照原样,其输出如下所示:

first
\nnewline
\ttab
 spacefoobar
\0null

所以,如果我想要foobar

printf ... | od ... | sed ... | 
sed 's/foobar/\
&\
/g'

###OUTPUT###

first
\nnewline
\ttab
 space
foobar

\0null

现在,如果您想使用C转义符,这非常简单-因为sed已经有双\\反斜杠对所有单输入反斜杠进行了转义,因此printf执行from xargs将不会产生问题,不会产生符合规范的输出。但是xargs 要吃壳引号,因此您需要再次将其双引号:

printf 'nl\ntab\tspace foobarfoobar\0null' |
PIPELINE |
sed 's/./\\&/g' | 
xargs printf %b | 
cat -A

###OUTPUT###

nl$
tab^Ispace $
foobar$
$
foobar$
^@null%

那可以很容易地保存到shell变量中,然后以相同的方式输出。最后sed一个\在输入的每个字符之前插入一个反斜杠,仅此而已。

这就是它掌握之前sed的样子:

printf 'nl\ntab\tspace foobarfoobar\0null' |
od -A n -t c -v -w1

   n
   l
  \n
   t
   a
   b
  \t
   s
   p
   a
   c
   e

   f
   o
   o
   b
   a
   r
   f
   o
   o
   b
   a
   r
  \0
   n
   u
   l
   l

2

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

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

可以有效地选择不同的字符作为记录分隔其他工具(sortsed通过与该字符交换换行,...) tr

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

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

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.