【问题标题】:Optimize sed for multiple replacements针对多个替换优化 sed
【发布时间】:2016-10-07 18:28:15
【问题描述】:

我有一个文件,users.txt,里面有类似的字眼,

user1
user2
user3

我想在另一个文件data.txt 中找到这些单词并为其添加前缀。 data.txt 有近 500K 行。例如,user1 应替换为 New_user1 等等。我写了简单的shell脚本,比如

for user in `cat users.txt`
do
    sed -i 's/'${user}'/New_&/' data.txt
done

对于大约 1000 个单词,这个程序需要几分钟的时间来处理,这让我感到惊讶,因为 sed 在查找和替换时非常快。我尝试参考Optimize shell script for multiple sed replacements,但仍然没有观察到多少改进。

有没有其他方法可以加快这个过程?

【问题讨论】:

    标签: bash shell unix sed


    【解决方案1】:

    众所周知,Sed 非常快(可能只比 C 差)。

    试试sed '/X/ s/X/Y/g' input.txt,而不是sed 's/X/Y/g' input.txt。后者速度更快。

    由于您只有“一次一行语义”,您可以像这样使用parallel(在多核 cpu-s 上)运行它:

    cat huge-file.txt | parallel --pipe sed -e '/xxx/ s/xxx/yyy/g'
    

    如果您使用的是纯 ascii 文件,则可以使用“C”语言环境加快速度:

    LC_ALL=C sed -i -e '/xxx/ s/xxx/yyy/g' huge-file.txt
    

    【讨论】:

    • 感谢您的回答。它真的很有帮助:)
    【解决方案2】:

    您可以将您的 users.txt 转换为 sed 命令,如下所示:

    $ sed 's|.*|s/&/New_&/|' users.txt 
    s/user1/New_user1/
    s/user2/New_user2/
    s/user3/New_user3/
    

    然后使用它来处理data.txt,或者通过将前一个命令的输出写入中间文件,或者使用进程替换:

    sed -f <(sed 's|.*|s/&/New_&/|' users.txt) data.txt
    

    您的方法针对users.txt 中的每一行都遍历了所有data.txt,这使它变慢了。

    如果你不能使用进程替换,你可以使用

    sed 's|.*|s/&/New_&/|' users.txt | sed -f - data.txt
    

    改为。

    【讨论】:

    • 感谢本杰明的快速回答 :)。我已经尝试过这种方法,但仍然需要将近 1 分钟才能完成 users.txt 中的约 1000 个条目
    • @user3150037 我不认为你可以使用 sed 变得更快 - 它仍然必须通过所有 data.txt 并尝试所有替换。一种更快的方法是找到一个描述users.txt 中所有单词的模式,然后你可以只使用一个替换。不过,我们必须看到更多 users.txt 的真实数据。
    • users.txt 是真实数据,但有很多条目,data.txt 也有类似的数据,但用户范围非常大(~500K)。
    • @user3150037 那么我认为 sed 不能让你更快地获得任何东西。 awk 或 Perl 通常更快。
    • jami、ben 和 benjamin 的名字有冲突怎么办?
    【解决方案3】:

    或者.. 一口气,我们可以做这样的事情。假设我们有一个包含 500k 行的数据文件。

    $>    
    wc -l data.txt
    500001 data.txt
    
    $>    
    ls -lrtha data.txt
    -rw-rw-r--. 1 gaurav gaurav 16M Oct  5 00:25 data.txt
    
    $>
    head -2 data.txt  ; echo ; tail -2 data.txt
    0|This is a test file maybe
    1|This is a test file maybe
    
    499999|This is a test file maybe
    500000|This is a test file maybe
    

    假设我们的 users.txt 有 3-4 个关键字,在文件“data.txt”中以“ab_”为前缀

    $>    
    cat users.txt
    file
    maybe
    test
    

    所以我们想要读取 users.txt 并且对于每个单词,我们想要将该单词更改为一个新单词。例如,“file”到“ab_file”,“maybe”到“ab_maybe”..

    我们可以运行一个while循环,一个一个地读取输入词作为前缀,然后我们对文件运行一个perl命令,输入词存储在一个变量中。在下面的示例中,读取的单词作为 $word 传递给 perl 命令。

    我对这个任务进行了计时,这发生得相当快。是在我的 Windows 10 上托管的虚拟机上完成的(使用 Centos7)。

    time cat users.txt |while read word; do  perl -pi -e "s/${word}/ab_${word}/g" data.txt; done        
    real    0m1.973s
    user    0m1.846s
    sys     0m0.127s
    $>    
    head -2 data.txt  ; echo ; tail -2 data.txt
    0|This is a ab_test ab_file ab_maybe
    1|This is a ab_test ab_file ab_maybe
    
    499999|This is a ab_test ab_file ab_maybe
    500000|This is a ab_test ab_file ab_maybe
    

    在上面的代码中,我们读到了test,file,maybe这些词,并在data.txt文件中将其改为ab_test,ab_file,ab_maybe。头部和尾部计数确认了我们的操作。

    干杯, 高拉夫

    【讨论】:

    • 你的格式让你很难理解你的答案。
    • 嗨,我很抱歉。我希望改善这一点。这是我作为适当用户在 stackoverflow 网站上的第一天。我在学习。感谢您的评论。现在,我已经删除了额外的粗体字体。有什么进一步的建议吗?高拉夫
    • 欢迎来到 SO!有宾至如归的感觉。
    • 谢谢黑笔。
    • 感谢您的详细解释和回答。
    猜你喜欢
    • 1970-01-01
    • 2014-02-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多