我试图仅在一个只有一行(2.1 GB)的巨大文件中替换一个字符串的第一个匹配项,这种替换将在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_STRING
总是在这个巨大的1行文件的开头,前100个字符。其他好处是执行时间不是问题,可能需要时间没有问题。
编辑(解决方案):
我测试了答案的三个解决方案,所有这些都解决了问题,谢谢你们所有人。我使用Linux time
测试了性能,所有这些都花费了大约相同的时间,大约10秒......但我选择@Miller解决方案因为它更简单(只使用{{1 }})。
答案 0 :(得分:6)
由于您知道您的字符串始终位于文件的第一个块中,因此您应该使用dd
。
您还需要一个临时文件,例如tmpfile="$(mktemp)"
首先,将文件的第一个块复制到一个新的临时位置:
dd bs=32k count=1 if=file.txt of="$tmpfile"
然后,在该块上进行替换:
sed -i 's/FROM_STRING/TO_STRING/' "$tmpfile"
接下来,再次使用dd
将新的第一个块与旧文件的其余部分连接起来:
dd bs=32k if=file.txt of="$tmpfile" seek=1 skip=1
编辑:根据Mark Setchell的建议,我在这些命令中添加了bs=32k
的规范,以加快dd
操作的速度。根据您的需要,这是可调的,但如果明确地调整单独的命令,您可能需要注意不同输入和输出块大小之间语义的变化。
答案 1 :(得分:2)
如果你确定你要替换的字符串只是前100个字符,那么下面的perl one-liner应该可以工作:
perl -i -pe 'BEGIN {$/ = \1024} s/FROM_STRING/TO_STRING/ .. undef' file.txt
切换:
-i
:编辑<>
个文件(如果提供了扩展程序,则进行备份)-p
:为输入文件中的每个“行”创建一个while(<>){...; print}
循环。 -e
:告诉perl
在命令行上执行代码。 <强>代码强>:
BEGIN {$/ = \1024}
:将$INPUT_RECORD_SEPARATOR设置为每个“行”的字符数s/FROM/TO/ .. undef
:使用flip-flop仅执行一次正则表达式。也可以使用if $. == 1
。答案 2 :(得分:1)
sysread
来读取大块,否则Perl IO很慢,binmode
不需要 [2] ,我使用
( head -c 100 | perl -0777pe's/.../.../' && cat ) <file.old >file.new