AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / unix / 问题 / 413664
Accepted
Christos Baziotis
Christos Baziotis
Asked: 2017-12-30 06:58:33 +0800 CST2017-12-30 06:58:33 +0800 CST 2017-12-30 06:58:33 +0800 CST

在一个巨大的(70GB)、一行、文本文件中替换字符串

  • 772

我有一个巨大的(70GB),一行文本文件,我想替换其中的一个字符串(令牌)。我想<unk>用另一个虚拟令牌(手套问题)替换令牌。

我试过sed:

sed 's/<unk>/<raw_unk>/g' < corpus.txt > corpus.txt.new

但输出文件corpus.txt.new有零字节!

我也尝试使用 perl:

perl -pe 's/<unk>/<raw_unk>/g' < corpus.txt > corpus.txt.new

但我遇到了内存不足的错误。

对于较小的文件,上述两个命令都有效。

如何替换字符串是这样的文件? 这是一个相关的问题,但没有一个答案对我有用。

编辑:如何将文件分成 10GB(或其他)的块并应用sed到每个块上,然后将它们合并cat?那有意义吗?有没有更优雅的解决方案?

text-processing sed
  • 14 14 个回答
  • 31321 Views

14 个回答

  • Voted
  1. JJoao
    2017-12-30T08:40:53+08:002017-12-30T08:40:53+08:00

    对于这么大的文件,一种可能是 Flex。让unk.l:

    %%
    \<unk\>     printf("<raw_unk>");  
    %%
    

    然后编译并执行:

    $ flex -o unk.c  unk.l
    $ cc -o unk -O2 unk.c -lfl
    $ unk < corpus.txt > corpus.txt.new
    
    • 113
  2. Best Answer
    Gilles 'SO- stop being evil'
    2017-12-30T07:07:42+08:002017-12-30T07:07:42+08:00

    通常的文本处理工具并非旨在处理不适合 RAM 的行。他们倾向于通过读取一条记录(一行)、操作它并输出结果,然后继续处理下一条记录(行)来工作。

    如果有一个 ASCII 字符经常出现在文件中而没有出现在<unk>or<raw_unk>中,那么您可以使用它作为记录分隔符。由于大多数工具不允许自定义记录分隔符,因此在该字符和换行符之间进行交换。tr处理字节,而不是行,因此它不关心任何记录大小。假设;有效:

    <corpus.txt tr '\n;' ';\n' |
    sed 's/<unk>/<raw_unk>/g' |
    tr '\n;' ';\n' >corpus.txt.new
    

    您还可以锚定在您正在搜索的文本的第一个字符上,假设它在搜索文本中没有重复并且它出现的频率足够高。如果文件可能以 开头unk>,请将 sed 命令更改为sed '2,$ s/…以避免虚假匹配。

    <corpus.txt tr '\n<' '<\n' |
    sed 's/^unk>/raw_unk>/g' |
    tr '\n<' '<\n' >corpus.txt.new
    

    或者,使用最后一个字符。

    <corpus.txt tr '\n>' '>\n' |
    sed 's/<unk$/<raw_unk/g' |
    tr '\n>' '>\n' >corpus.txt.new
    

    请注意,此技术假定 sed 对不以换行符结尾的文件无缝运行,即它处理最后部分行而不截断它并且不附加最终换行符。它适用于 GNU sed。如果您可以选择文件的最后一个字符作为记录分隔符,您将避免任何可移植性问题。

    • 111
  3. sourcejedi
    2017-12-30T13:44:07+08:002017-12-30T13:44:07+08:00

    因此,您没有足够的物理内存 (RAM) 来一次保存整个文件,但在 64 位系统上,您有足够的虚拟地址空间来映射整个文件。在这种情况下,虚拟映射可以用作简单的 hack。

    必要的操作都包含在 Python 中。有一些令人讨厌的细微之处,但它确实避免了编写 C 代码。特别是,需要注意避免将文件复制到内存中,这将完全破坏这一点。从好的方面来说,您可以免费获得错误报告(python“异常”):)。

    #!/usr/bin/python3
    # This script takes input from stdin
    # (but it must be a regular file, to support mapping it),
    # and writes the result to stdout.
    
    search = b'<unk>'
    replace = b'<raw_unk>'
    
    
    import sys
    import os
    import mmap
    
    # sys.stdout requires str, but we want to write bytes
    out_bytes = sys.stdout.buffer
    
    mem = mmap.mmap(sys.stdin.fileno(), 0, access=mmap.ACCESS_READ)
    i = mem.find(search)
    if i < 0:
        sys.exit("Search string not found")
    
    # mmap object subscripts to bytes (making a copy)
    # memoryview object subscripts to a memoryview object
    # (it implements the buffer protocol).
    view = memoryview(mem)
    
    out_bytes.write(view[:i])
    out_bytes.write(replace)
    out_bytes.write(view[i+len(search):])
    
    • 41
  4. legolegs
    2017-12-30T13:11:07+08:002017-12-30T13:11:07+08:00

    replacemariadb-server/mysql-server 包中有一个实用程序。它替换简单的字符串(不是正则表达式),并且不像 grep/sed/awkreplace不关心\nand \0。任何输入文件(我的机器上大约 400kb)的内存消耗都是恒定的。

    当然你不需要运行 mysql 服务器来使用replace,它只是在 Fedora 中以这种方式打包的。其他发行版/操作系统可能会将其单独打包。

    • 17
  5. Patrick Bucher
    2017-12-30T12:14:52+08:002017-12-30T12:14:52+08:00

    我认为 C 版本的性能可能会更好:

    #include <stdio.h>
    #include <string.h>
    
    #define PAT_LEN 5
    
    int main()
    {
        /* note this is not a general solution. In particular the pattern
         * must not have a repeated sequence at the start, so <unk> is fine
         * but aardvark is not, because it starts with "a" repeated, and ababc
         * is not because it starts with "ab" repeated. */
        char pattern[] = "<unk>";          /* set PAT_LEN to length of this */
        char replacement[] = "<raw_unk>"; 
        int c;
        int i, j;
    
        for (i = 0; (c = getchar()) != EOF;) {
            if (c == pattern[i]) {
                i++;
                if (i == PAT_LEN) {
                    printf("%s", replacement);
                    i = 0;
                }
            } else {
                if (i > 0) {
                    for (j = 0; j < i; j++) {
                        putchar(pattern[j]);
                    }
                    i = 0;
                }
                if (c == pattern[0]) {
                    i = 1;
                } else {
                    putchar(c);
                }
            }
        }
        /* TODO: fix up end of file if it ends with a part of pattern */
        return 0;
    }
    

    编辑:根据评论中的建议进行修改。还修复了该模式的错误<<unk>。

    • 16
  6. meuh
    2017-12-30T08:37:15+08:002017-12-30T08:37:15+08:00

    GNUgrep可以显示“二进制”文件中匹配的偏移量,而无需将整行读入内存。然后,您可以使用dd最多读取此偏移量,跳过匹配项,然后继续从文件复制。

    file=...
    newfile=...
    replace='<raw_unk>'
    grep -o -b -a -F '<unk>' <"$file" |
    (   pos=0
        while IFS=$IFS: read offset pattern
        do size=${#pattern}
           let skip=offset-pos
           let big=skip/1048576
           let skip=skip-big*1048576
           dd bs=1048576 count=$big <&3
           dd bs=1 count=$skip <&3
           dd bs=1 count=$size of=/dev/null <&3
           printf "%s" "$replace"
           let pos=offset+size
        done
        cat <&3
    ) 3<"$file" >"$newfile"
    

    为了速度,我将它dd分成块大小为 1048576 的大读取和一次 1 字节的较小读取,但是对于这么大的文件,这个操作仍然会有点慢。grep例如,输出是 ,并且13977:<unk>通过读取到变量offset和,在冒号上将其拆分pattern。我们必须跟踪pos已经从文件中复制了多少字节。

    • 14
  7. alfreema
    2017-12-31T08:30:13+08:002017-12-31T08:30:13+08:00

    这是另一个可能比其他选项执行得更好的单个 UNIX 命令行,因为您可以“寻找”执行良好的“块大小”。为了使它健壮,您需要知道每个 X 字符中至少有一个空格,其中 X 是您的任意“块大小”。在下面的示例中,我选择了 1024 个字符的“块大小”。

    fold -w 1024 -s corpus.txt | sed 's/<unk>/<raw_unk>/g' | tr '/n' '/0'
    

    在这里, fold最多可以抓取1024 个字节,但是 -s 确保它在空间上中断,如果自上次中断后至少有一个。

    sed 命令是您的命令,可以执行您所期望的操作。

    然后 tr 命令将“展开”文件,将插入的换行符转换为空。

    您应该考虑尝试更大的块大小,看看它是否执行得更快。对于 fold 的 -w 选项,您可以尝试使用 10240 和 102400 和 1048576 而不是 1024。

    这是一个将所有 N 转换为小写的每个步骤分解的示例:

    [root@alpha ~]# cat mailtest.txt
    test XJS C4JD QADN1 NSBN3 2IDNEN GTUBE STANDARD ANTI UBE-TEST EMAIL*C.34X test
    
    [root@alpha ~]# fold -w 20 -s mailtest.txt
    test XJS C4JD QADN1
    NSBN3 2IDNEN GTUBE
    STANDARD ANTI
    UBE-TEST
    EMAIL*C.34X test
    
    [root@alpha ~]# fold -w 20 -s mailtest.txt | sed 's/N/n/g'
    test XJS C4JD QADn1
    nSBn3 2IDnEn GTUBE
    STAnDARD AnTI
    UBE-TEST
    EMAIL*C.34X test
    
    [root@alpha ~]# fold -w 20 -s mailtest.txt | sed 's/N/n/g' | tr '\n' '\0'
    test XJS C4JD QADn1 nSBn3 2IDnEn GTUBE STAnDARD AnTI UBE-TEST EMAIL*C.34X test
    

    如果有文件,您将需要在文件的最后添加一个换行符,因为 tr 命令将删除它。

    • 11
  8. Evan Carroll
    2017-12-30T12:47:20+08:002017-12-30T12:47:20+08:00

    使用perl

    管理自己的缓冲区

    您可以使用IO::Handle's管理默认缓冲区,也可以使用和setvbuf管理您自己的缓冲区。检查并获取更多信息,基本上他们跳过缓冲 io。sysreadsyswriteperldoc -f sysreadperldoc -f syswrite

    这里我们滚动我们自己的缓冲区 IO,但是我们在 1024 字节上手动任意执行。我们还为 RW 打开文件,因此我们一次在同一个 FH 上完成所有操作。

    use strict;
    use warnings;
    use Fcntl qw(:flock O_RDWR);
    use autodie;
    use bytes;
    
    use constant CHUNK_SIZE => 1024 * 32;
    
    sysopen my $fh, 'file', O_RDWR;
    flock($fh, LOCK_EX);
    
    my $chunk = 1;
    while ( sysread $fh, my $bytes, CHUNK_SIZE * $chunk ) {
      if ( $bytes =~ s/<unk>/<raw_unk>/g ) {
        seek( $fh, ($chunk-1)* CHUNK_SIZE, 0 );
        syswrite( $fh, $bytes, 1024);
        seek( $fh, $chunk * CHUNK_SIZE, 0 );
      }
      $chunk++;
    }
    

    如果你要走这条路

    1. 确保<unk>和<raw_unk>是相同的字节大小。
    2. CHUNKSIZE如果要替换超过 1 个字节,您可能需要确保我们的缓冲方法不会越界。
    • 10
  9. ovirt
    2017-12-31T18:52:00+08:002017-12-31T18:52:00+08:00

    你可以试试bbe(二进制块编辑器),一个“sed二进制文件”。

    我在一个没有字符的 7GB 文本文件上使用它取得了很好的成功EOL,用不同长度的一个字符串替换了多次出现的字符串。在没有尝试任何优化的情况下,它给出了 > ​​50MB/s 的平均处理吞吐量。

    • 10
  10. Patrick Bucher
    2017-12-30T07:58:49+08:002017-12-30T07:58:49+08:00

    这是一个执行任务的小型 Go 程序 ( unk.go):

    package main
    
    import (
        "bufio"
        "fmt"
        "log"
        "os"
    )
    
    func main() {
        const (
            pattern     = "<unk>"
            replacement = "<raw_unk>"
        )
        var match int
        var char rune
        scanner := bufio.NewScanner(os.Stdin)
        scanner.Split(bufio.ScanRunes)
        for scanner.Scan() {
            char = rune(scanner.Text()[0])
            if char == []rune(pattern)[match] {
                match++
                if match == len(pattern) {
                    fmt.Print(replacement)
                    match = 0
                }
            } else {
                if match > 0 {
                    fmt.Print(string(pattern[:match]))
                    match = 0
                }
                if char == rune(pattern[0]) {
                    match = 1
                } else {
                    fmt.Print(string(char))
                }
            }
        }
        if err := scanner.Err(); err != nil {
            log.Fatal(err)
        }
    }
    

    只需构建它go build unk.go并以./unk <input >output.

    编辑:

    抱歉,我没有读到所有内容都在一行中,所以我现在尝试逐个字符地读取文件。

    编辑二:

    应用与 C 程序相同的修复程序。

    • 5

相关问题

  • 如何删除两行之间的单行

  • 重新排列字母并比较两个单词

  • 在awk中的两行之间减去相同的列

  • 多行文件洗牌

  • 如何更改字符大小写(从小到大,反之亦然)?同时[重复]

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    JSON数组使用jq来bash变量

    • 4 个回答
  • Marko Smith

    日期可以为 GMT 时区格式化当前时间吗?[复制]

    • 2 个回答
  • Marko Smith

    bash + 通过 bash 脚本从文件中读取变量和值

    • 4 个回答
  • Marko Smith

    如何复制目录并在同一命令中重命名它?

    • 4 个回答
  • Marko Smith

    ssh 连接。X11 连接因身份验证错误而被拒绝

    • 3 个回答
  • Marko Smith

    如何下载软件包而不是使用 apt-get 命令安装它?

    • 7 个回答
  • Marko Smith

    systemctl 命令在 RHEL 6 中不起作用

    • 3 个回答
  • Marko Smith

    rsync 端口 22 和 873 使用

    • 2 个回答
  • Marko Smith

    以 100% 的利用率捕捉 /dev/loop -- 没有可用空间

    • 1 个回答
  • Marko Smith

    jq 打印子对象中所有的键和值

    • 2 个回答
  • Martin Hope
    EHerman JSON数组使用jq来bash变量 2017-12-31 14:50:58 +0800 CST
  • Martin Hope
    Christos Baziotis 在一个巨大的(70GB)、一行、文本文件中替换字符串 2017-12-30 06:58:33 +0800 CST
  • Martin Hope
    Drux 日期可以为 GMT 时区格式化当前时间吗?[复制] 2017-12-26 11:35:07 +0800 CST
  • Martin Hope
    AllisonC 如何复制目录并在同一命令中重命名它? 2017-12-22 05:28:06 +0800 CST
  • Martin Hope
    Steve “root”用户的文件权限如何工作? 2017-12-22 02:46:01 +0800 CST
  • Martin Hope
    Bagas Sanjaya 为什么 Linux 使用 LF 作为换行符? 2017-12-20 05:48:21 +0800 CST
  • Martin Hope
    Cbhihe 将默认编辑器更改为 vim for _ sudo systemctl edit [unit-file] _ 2017-12-03 10:11:38 +0800 CST
  • Martin Hope
    showkey 如何下载软件包而不是使用 apt-get 命令安装它? 2017-12-03 02:15:02 +0800 CST
  • Martin Hope
    youxiao 为什么目录 /home、/usr、/var 等都具有相同的 inode 编号 (2)? 2017-12-02 05:33:41 +0800 CST
  • Martin Hope
    user223600 gpg —list-keys 命令在将私钥导入全新安装后输出 uid [未知] 2017-11-26 18:26:02 +0800 CST

热门标签

linux bash debian shell-script text-processing ubuntu centos shell awk ssh

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve