我最近问了一个问题,如果换行符出现在另一个特定字符之后,该如何删除。
Unix文本处理工具非常强大,但是几乎所有工具都处理文本行,这在输入适合可用内存的大多数情况下都是可以的。
但是,如果我希望在不包含换行符的巨大文件中替换文本序列,该怎么办?
例如替换<foobar>
为\n<foobar>
而不逐行读取输入?(因为只有一行,而且长度为2.5G个字符)。
gsar
(home.online.no/~tjaberg)我会尝试的。
我最近问了一个问题,如果换行符出现在另一个特定字符之后,该如何删除。
Unix文本处理工具非常强大,但是几乎所有工具都处理文本行,这在输入适合可用内存的大多数情况下都是可以的。
但是,如果我希望在不包含换行符的巨大文件中替换文本序列,该怎么办?
例如替换<foobar>
为\n<foobar>
而不逐行读取输入?(因为只有一行,而且长度为2.5G个字符)。
gsar
(home.online.no/~tjaberg)我会尝试的。
Answers:
面对此类问题时,我发生的第一件事是更改记录分隔符。在大多数工具中,\n
默认情况下将其设置为默认值,但可以更改。例如:
佩尔
perl -0x3E -pe 's/<foobar>/\n$&/' file
-0
:将输入记录分隔符设置为给定十六进制值的字符。在这种情况下,我将>
其设置为十六进制值为3E
。一般格式为-0xHEX_VALUE
。这只是将行划分为可管理块的一个技巧。-pe
:应用给出的脚本后,打印每行输入-e
。s/<foobar>/\n$&/
:一个简单的替代。该$&
是什么是匹配的,在这种情况下<foobar>
。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
awk
和perl
使用的内存量都可以忽略不计。
Tie::File
是自以来的核心模块v5.7.3
。
gsar (一般搜索和替换)是用于此目的的非常有用的工具。
该问题的大多数答案都使用基于记录的工具和各种技巧来使它们适应问题,例如将默认的记录分隔符切换为假定在输入中经常出现的字符,而不会使每个记录太大而无法处理。
在许多情况下,这非常好,甚至可读。我不喜欢的问题,可以很容易地/一个到处可用的工具,如有效的解决awk
,tr
,sed
和Bourne shell。
对于这些标准的UNIX工具,执行二进制搜索并替换为具有随机内容的任意大文件并不十分合适。
你们中的有些人可能认为这是作弊,但我看不出使用正确的工具完成工作会是错误的。在这种情况下,这是一个gsar
根据GPL v2授权的名为C的C程序,因此令我惊讶的是,gentoo,redhat和ubuntu中都没有针对该非常有用的工具的软件包。
gsar
使用Boyer-Moore字符串搜索算法的二进制变体。
用法很简单:
gsar -F '-s<foobar>' '-r:x0A<foobar>'
其中-F
表示“过滤器”模式,即对进行stdin
读写stdout
。也有一些对文件进行操作的方法。-s
指定搜索字符串和-r
替换。冒号可以用来指定任意字节值。
支持不区分大小写的模式(-i
),但不支持正则表达式,因为该算法使用搜索字符串的长度来优化搜索。
该工具也可以仅用于搜索,类似于grep
。gsar -b
输出匹配搜索字符串的字节偏移,和gsar -l
打印文件名和如果有的话,有点像组合匹配的数目grep -l
与wc
。
该工具由Tormod Tjaberg(初始)和Hans Peter Verne(改进)编写。
gsar
。
在目标字符串和替换字符串长度相同的狭窄情况下,可以使用内存映射。如果需要就地执行更换,这将特别有用。您基本上是在将文件映射到进程的虚拟内存中,并且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)
有许多用于此的工具:
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}'
上面的那一点在\n
ewlines,\0
null,\t
abs 上定界,<spaces>
同时\C
为定界符保留了转义的字符串。请注意所使用的H
和x
函数-每次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
Awk对连续记录进行操作。它可以使用任何字符作为记录分隔符(许多实现中的空字节除外)。某些实现支持将任意正则表达式(不匹配空字符串)作为记录分隔符,但这可能很麻烦,因为在将记录分隔符存放到其中之前,它会从每个记录的末尾截断$0
(GNU awk将变量RT
设置为记录分隔符)从当前记录的末尾删除)。注意,print
它的输出以输出记录分隔符终止,该输出记录分隔ORS
符默认为换行符,并且与输入记录分隔符独立设置RS
。
awk -v RS=, 'NR==1 {printf "input up to the first comma: %s\n", $0}'
可以有效地选择不同的字符作为记录分隔其他工具(sort
,sed
通过与该字符交换换行,...) tr
。
tr '\n,' ',\n' |
sed 's/foo/bar/' |
sort |
tr '\n,' ',\n'
许多GNU文本实用程序支持使用空字节而不是换行符作为分隔符。
perl
或python
吗?