Regex 在Linux上替换1行2GB文件中的第一个字符串匹配

Regex 在Linux上替换1行2GB文件中的第一个字符串匹配,regex,bash,perl,sed,substitution,Regex,Bash,Perl,Sed,Substitution,我试图用一行2.1GB替换一个大文件中一个字符串的第一个匹配项,这种替换将发生在shell脚本作业中。最大的问题是运行此脚本的机器只有大约1GB的内存 300MB的空闲空间,所以我需要一个不会溢出内存的缓冲策略。我已经尝试过sed、perl和python方法,但它们都返回内存不足错误。 以下是我在其他问题中发现的尝试: # With perl perl -pi -e '!$x && s/FROM_STRING/TO_STRING/ && ($x=1)' file.

我试图用一行2.1GB替换一个大文件中一个字符串的第一个匹配项,这种替换将发生在shell脚本作业中。最大的问题是运行此脚本的机器只有大约1GB的内存 300MB的空闲空间,所以我需要一个不会溢出内存的缓冲策略。我已经尝试过sed、perl和python方法,但它们都返回内存不足错误。 以下是我在其他问题中发现的尝试:

# With perl
perl -pi -e '!$x && s/FROM_STRING/TO_STRING/ && ($x=1)' file.txt

# With sed
sed '0,/FROM_STRING/s//TO_STRING/' file.txt > file.txt.bak

# With python (in a custom script.py file)
for line in fileinput.input('file.txt', inplace=True):
    print line.replace(FROM_STRING, TO_STRING, 1)
    break
一个好的方面是,我正在搜索的FROM_字符串总是在这个巨大的1行文件的开头,在前100个字符中。另一个好处是执行时间不是问题,它可以毫无问题地花费时间

编辑解决方案:


我测试了三个答案,所有答案都解决了问题,谢谢大家。我用Linux时间测试了性能,所有这些测试都花费了大约相同的时间,大约10秒。。。但是我选择@Miller解决方案,因为它更简单,只使用perl。

如果您确定要替换的字符串仅在前100个字符中,那么下面的perl one liner应该可以工作:

perl -i -pe 'BEGIN {$/ = \1024} s/FROM_STRING/TO_STRING/ .. undef' file.txt
说明: 开关:

-i:就地编辑文件如果提供扩展名,则进行备份 -p:为输入文件中的每一行创建一个while{…;print}循环。 -e:告诉perl在命令行上执行代码。 代码:

开始{$/=\1024}:将设置为每个“行”要读取的字符数 s/FROM/TO/。。undef:仅使用一次来执行正则表达式。如果$.=,也可以使用1.
如果您确定要替换的字符串仅在前100个字符中,那么以下perl one行程序应该可以工作:

perl -i -pe 'BEGIN {$/ = \1024} s/FROM_STRING/TO_STRING/ .. undef' file.txt
说明: 开关:

-i:就地编辑文件如果提供扩展名,则进行备份 -p:为输入文件中的每一行创建一个while{…;print}循环。 -e:告诉perl在命令行上执行代码。 代码:

开始{$/=\1024}:将设置为每个“行”要读取的字符数 s/FROM/TO/。。undef:仅使用一次来执行正则表达式。如果$.=,也可以使用1.
未经测试,但我会:

perl -pi -we 'BEGIN{$/=\65536} s/FROM_STRING/TO_STRING/ if 1..1' file.txt

阅读64k块。

未经测试,但我会:

perl -pi -we 'BEGIN{$/=\65536} s/FROM_STRING/TO_STRING/ if 1..1' file.txt

读取64k块。

因为您知道字符串始终位于文件的第一个块中,所以应该使用dd。 您还需要使用一个临时文件,如tmpfile=$mktemp

首先,将文件的第一个块复制到新的临时位置: dd bs=32k count=1 if=file.txt of=$tmpfile

然后,在该块上进行替换: sed-i的/FROM_STRING/TO_STRING/'$tmpfile

接下来,再次使用dd将新的第一个块与旧文件的其余部分连接起来: dd bs=32k if=file.txt of=$tmpfile seek=1 skip=1


编辑:根据MarkSetchell的建议,我在这些命令中添加了bs=32k的规范,以加快dd操作的速度。这是可以根据您的需要进行调整的,但是如果单独调整命令,您可能需要注意不同输入和输出块大小之间的语义变化。

因为您知道字符串始终位于文件的第一个块中,所以应该使用dd。 您还需要使用一个临时文件,如tmpfile=$mktemp

首先,将文件的第一个块复制到新的临时位置: dd bs=32k count=1 if=file.txt of=$tmpfile

然后,在该块上进行替换: sed-i的/FROM_STRING/TO_STRING/'$tmpfile

接下来,再次使用dd将新的第一个块与旧文件的其余部分连接起来: dd bs=32k if=file.txt of=$tmpfile seek=1 skip=1


编辑:根据MarkSetchell的建议,我在这些命令中添加了bs=32k的规范,以加快dd操作的速度。这是可以根据您的需要进行调整的,但是如果单独调整命令,您可能需要注意不同输入和输出块大小之间的语义变化。

一个实用的方法是拆分文件,执行搜索替换和联接:例如:

head -c 100 myfile | sed 's/FROM/TO/' > output.1
tail -c +101 myfile > output.2
cat output.1 output.2 > output && /bin/rm output.1 output.2
或者,在一行中:

( ( head -c 100 myfile | sed 's/FROM/TO/' ) && (tail -c +101 myfile ) ) > output

一个实用的方法不是很紧凑,但是很有效,就是拆分文件,进行搜索替换并连接:例如:

head -c 100 myfile | sed 's/FROM/TO/' > output.1
tail -c +101 myfile > output.2
cat output.1 output.2 > output && /bin/rm output.1 output.2
或者,在一行中:

( ( head -c 100 myfile | sed 's/FROM/TO/' ) && (tail -c +101 myfile ) ) > output
假定要替换的字符串位于前100个字节中, 鉴于Perl IO速度很慢,除非您开始使用sysread读取大数据块, 假设替换更改了文件[1]的大小,并且 假设不需要binmode[2], 我会用

( head -c 100 | perl -0777pe's/.../.../' && cat ) <file.old >file.new
一个更快的解决方案已经存在。 不过,如果需要,很容易添加。 假定要替换的字符串位于前100个字节中, 鉴于Perl IO速度很慢,除非您开始使用sysread读取大数据块, 假设替换更改了文件[1]的大小,并且 假设不需要binmode[2], 我会用

( head -c 100 | perl -0777pe's/.../.../' && cat ) <file.old >file.new
一个更快的解决方案已经存在。 不过,如果需要,很容易添加。

如果启用了应该启用的警告,则会触发无害的警告,即使在onelinersAFAIK上也是如此-我并没有真正在原地编辑文件,它会复制文件,因此对于2G文件和1G内存是否真的有效?@Ed Morton,您将需要4GB的磁盘空间。如果启用了应该启用的警告,则会触发无害的警告,即使在onelinersAFAIK上,我也不会在原地编辑文件,它会复制文件,所以对于2G文件和1G内存来说,这真的有效吗?@Ed Morton,你需要4GB的磁盘空间。+1这是一个好方法,但是我会从一开始就减少32kB,然后使用32kB的块将文件放回一起,因为对于较大的块,dd通常要快得多。具体地说,我的意思是将bs=32k添加到dd命令中。+1是一个很好的方法,但我会从一开始就减少32kB,然后使用32kB块将文件放回一起,因为对于较大的块,dd通常要快得多。具体来说,我的意思是将bs=32k添加到dd命令中。@AvinashRaj,因为整个文件只有一行,因此仍将尝试将2个gig加载到内存中。匹配字符串有多长?替换字符串的长度是否相同?@AvinashRaj,因为整个文件只有一行,因此仍将尝试将2个gig加载到内存中。匹配字符串的长度是多少?替换字符串的长度相同吗?在注意到你的答案之前,我写了一个与你的答案非常相似的答案。然而,我的答案要少得多才能达到同样的效果。在注意到你的答案之前,我写了一个与你的答案非常相似的答案。然而,要达到同样的结果,我的工作要少得多。注意:这仍然是大多数人使用的8K块或4K块从磁盘读取的数据。使用sysread实际读取大数据块可以获得更好的性能。我可以看出,这几乎没有什么区别。注意:大多数人使用8K数据块或4K旧数据块从磁盘读取数据。使用sysread实际读取大数据块可以获得更好的性能;我假设尝试这样做会导致head丢弃一个部分块,但它似乎没有这个问题。@ysth,在答案的编辑历史记录中看到的我的代码的原始版本可以在您描述的系统上工作。FWIW,我看到这个答案和我的答案之间没有速度差异,虽然当时我正在系统上做其他事情。可能是我的第二次给予并不是真的给予;我假设尝试这样做会导致head丢弃一个部分块,但它似乎没有这个问题。@ysth,在答案的编辑历史记录中看到的我的代码的原始版本可以在您描述的系统上工作。FWIW,我看到这个答案和我的答案之间没有速度差异,虽然当时我在系统上做其他事情。可能是我的第二次给予并不是真的给予。