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

时间:2014-09-01 21:00:18

标签: regex bash perl sed substitution

我试图仅在一个只有一行(2.1 GB)的巨大文件中替换一个字符串的第一个匹配项,这种替换将在shell脚本作业中进行。最大的问题是运行此脚本的机器只有1GB内存(约  300MB免费),所以我需要一个不会溢出记忆的缓冲策略。我已经尝试了sedperlpython方法,但所有这些方法都让我失去了内存错误。 以下是我的尝试(在其他问题中发现):

# 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 }})。

3 个答案:

答案 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)

  • 鉴于那么要替换的字符串是前100个字节,
  • 鉴于除非您开始使用sysread来读取大块,否则Perl IO很慢,
  • 假设替换改变了文件 [1] 的大小,并且
  • 假设binmode不需要 [2]

我使用

( head -c 100 | perl -0777pe's/.../.../' && cat ) <file.old >file.new

  1. 存在更快的解决方案。
  2. 虽然如果需要可以轻松添加。