AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / unix / Perguntas / 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

Substitua a string em um arquivo de texto enorme (70 GB), uma linha

  • 772

Eu tenho um enorme (70 GB), uma linha , arquivo de texto e quero substituir uma string (token) nele. Desejo substituir o token <unk>por outro token fictício ( emissão de luva ).

eu tentei sed:

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

mas o arquivo de saída corpus.txt.newtem zero bytes!

Eu também tentei usar perl:

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

mas recebi um erro de falta de memória.

Para arquivos menores, ambos os comandos acima funcionam.

Como posso substituir uma string como um arquivo? Esta é uma questão relacionada, mas nenhuma das respostas funcionou para mim.

Editar : Que tal dividir o arquivo em pedaços de 10 GB (ou o que for) cada um e aplicar sedem cada um deles e depois mesclá-los com cat? Isso faz sentido? Existe uma solução mais elegante?

text-processing sed
  • 14 14 respostas
  • 31321 Views

14 respostas

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

    Para um arquivo tão grande, uma possibilidade é o Flex. Seja unk.l:

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

    Em seguida, compile e execute:

    $ 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

    As ferramentas usuais de processamento de texto não são projetadas para lidar com linhas que não cabem na RAM. Eles tendem a trabalhar lendo um registro (uma linha), manipulando-o e exibindo o resultado, passando para o próximo registro (linha).

    Se houver um caractere ASCII que aparece com frequência no arquivo e não aparece em <unk>ou <raw_unk>, você pode usá-lo como separador de registro. Como a maioria das ferramentas não permite separadores de registro personalizados, troque entre esse caractere e as novas linhas. trprocessa bytes, não linhas, então não se importa com nenhum tamanho de registro. Supondo que ;funcione:

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

    Você também pode ancorar no primeiro caractere do texto que está procurando, supondo que ele não seja repetido no texto de pesquisa e apareça com frequência suficiente. Se o arquivo começar com unk>, altere o comando sed sed '2,$ s/…para evitar uma correspondência espúria.

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

    Como alternativa, use o último caractere.

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

    Observe que esta técnica assume que o sed opera perfeitamente em um arquivo que não termina com uma nova linha, ou seja, que processa a última linha parcial sem truncá-la e sem acrescentar uma nova linha final. Funciona com GNU sed. Se você puder escolher o último caractere do arquivo como separador de registro, evitará problemas de portabilidade.

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

    Portanto, você não tem memória física (RAM) suficiente para armazenar todo o arquivo de uma vez, mas em um sistema de 64 bits, você tem espaço de endereço virtual suficiente para mapear o arquivo inteiro. Os mapeamentos virtuais podem ser úteis como um hack simples em casos como este.

    As operações necessárias estão todas incluídas no Python. Existem várias sutilezas irritantes, mas evita ter que escrever código C. Em particular, é necessário cuidado para evitar copiar o arquivo na memória, o que invalidaria totalmente o ponto. No lado positivo, você obtém relatórios de erros gratuitamente (python "exceções") :).

    #!/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

    Existe um replaceutilitário no pacote mariadb-server/mysql-server. Ele substitui strings simples (não expressões regulares) e, ao contrário de grep/sed/awk replace, não se preocupa com \nand \0. O consumo de memória é constante com qualquer arquivo de entrada (cerca de 400kb na minha máquina).

    É claro que você não precisa executar um servidor mysql para usar replaceo , ele é empacotado dessa forma apenas no Fedora. Outras distros/sistemas operacionais podem tê-lo empacotado separadamente.

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

    Acho que a versão C pode ter um desempenho muito melhor:

    #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;
    }
    

    EDIT: Modificado de acordo com as sugestões dos comentários. Também corrigido bug com o padrão <<unk>.

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

    O GNU greppode mostrar a você o deslocamento de correspondências em arquivos "binários", sem ter que ler linhas inteiras na memória. Você pode usar ddpara ler até esse deslocamento, pular a correspondência e continuar copiando do arquivo.

    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"
    

    Para velocidade, eu dividi ddem uma grande leitura de tamanho de bloco 1048576 e uma leitura menor de 1 byte por vez, mas esta operação ainda será um pouco lenta em um arquivo tão grande. A grepsaída é, por exemplo, 13977:<unk>, e isso é dividido nos dois pontos pela leitura em variáveis offset​​e pattern. Temos que acompanhar posquantos bytes já foram copiados do arquivo.

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

    Aqui está outra linha de comando única do UNIX que pode ter um desempenho melhor do que outras opções, porque você pode "caçar" um "tamanho de bloco" com bom desempenho. Para que isso seja robusto, você precisa saber que tem pelo menos um espaço em cada X caracteres, onde X é o seu "tamanho de bloco" arbitrário. No exemplo abaixo, escolhi um "tamanho do bloco" de 1024 caracteres.

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

    Aqui, fold pegará até 1024 bytes, mas o -s garante que ele quebre em um espaço se houver pelo menos um desde a última quebra.

    O comando sed é seu e faz o que você espera.

    Em seguida, o comando tr irá "desdobrar" o arquivo convertendo as novas linhas que foram inseridas de volta para nada.

    Você deve considerar tentar tamanhos de bloco maiores para ver se ele funciona mais rápido. Em vez de 1024, você pode tentar 10240 e 102400 e 1048576 para a opção -w de dobrar.

    Aqui está um exemplo dividido por cada etapa que converte todos os N's em minúsculas:

    [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
    

    Você precisará adicionar uma nova linha no final do arquivo, se houver, porque o comando tr irá removê-la.

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

    Usandoperl

    Gerenciando seus próprios buffers

    Você pode usar IO::Handlepara setvbufgerenciar os buffers padrão ou pode gerenciar seus próprios buffers com sysreade syswrite. Verifique perldoc -f sysreade perldoc -f syswritepara obter mais informações, essencialmente, eles ignoram o buffer io.

    Aqui, rolamos nosso próprio buffer IO, mas o fazemos manualmente e arbitrariamente em 1024 bytes. Também abrimos o arquivo para RW, então fazemos tudo no mesmo FH de uma vez.

    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++;
    }
    

    Se você está indo para este caminho

    1. Certifique -se de que <unk>e <raw_unk>são do mesmo tamanho de byte.
    2. Você pode querer certificar-se de que nosso método em buffer não ultrapasse o CHUNKSIZElimite, se estiver substituindo mais de 1 byte.
    • 10
  9. ovirt
    2017-12-31T18:52:00+08:002017-12-31T18:52:00+08:00

    Você pode tentar o bbe ( editor de blocos binários ), um " sedpara arquivos binários".

    Tive um bom sucesso ao usá-lo em um arquivo de texto de 7 GB sem EOLcaracteres, substituindo várias ocorrências de uma string por uma de comprimento diferente. Sem tentar nenhuma otimização, ele forneceu uma taxa de transferência média de processamento de > 50 MB/s.

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

    Aqui está um pequeno programa Go que executa a tarefa ( 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)
        }
    }
    

    Basta construí-lo go build unk.goe executá-lo como ./unk <input >output.

    EDITAR:

    Desculpe, não li que está tudo em uma linha, então tentei ler o arquivo caractere por caractere agora.

    EDIÇÃO II:

    Aplicou a mesma correção do programa C.

    • 5

relate perguntas

  • Como remover uma única linha entre duas linhas

  • Reorganize as letras e compare duas palavras

  • Subtraindo a mesma coluna entre duas linhas no awk

  • Embaralhamento de arquivo de várias linhas

  • como posso alterar o caso do caractere (de baixo para cima e vice-versa)? ao mesmo tempo [duplicado]

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Matriz JSON para bash variáveis ​​usando jq

    • 4 respostas
  • Marko Smith

    A data pode formatar a hora atual para o fuso horário GMT? [duplicado]

    • 2 respostas
  • Marko Smith

    bash + lê variáveis ​​e valores do arquivo pelo script bash

    • 4 respostas
  • Marko Smith

    Como posso copiar um diretório e renomeá-lo no mesmo comando?

    • 4 respostas
  • Marko Smith

    conexão ssh. Conexão X11 rejeitada devido a autenticação incorreta

    • 3 respostas
  • Marko Smith

    Como baixar o pacote não instalá-lo com o comando apt-get?

    • 7 respostas
  • Marko Smith

    comando systemctl não funciona no RHEL 6

    • 3 respostas
  • Marko Smith

    rsync porta 22 e 873 uso

    • 2 respostas
  • Marko Smith

    snap /dev/loop em 100% de utilização -- sem espaço livre

    • 1 respostas
  • Marko Smith

    chave de impressão jq e valor para todos no subobjeto

    • 2 respostas
  • Martin Hope
    EHerman Matriz JSON para bash variáveis ​​usando jq 2017-12-31 14:50:58 +0800 CST
  • Martin Hope
    Christos Baziotis Substitua a string em um arquivo de texto enorme (70 GB), uma linha 2017-12-30 06:58:33 +0800 CST
  • Martin Hope
    Drux A data pode formatar a hora atual para o fuso horário GMT? [duplicado] 2017-12-26 11:35:07 +0800 CST
  • Martin Hope
    AllisonC Como posso copiar um diretório e renomeá-lo no mesmo comando? 2017-12-22 05:28:06 +0800 CST
  • Martin Hope
    Steve Como as permissões de arquivo funcionam para o usuário "root"? 2017-12-22 02:46:01 +0800 CST
  • Martin Hope
    Bagas Sanjaya Por que o Linux usa LF como caractere de nova linha? 2017-12-20 05:48:21 +0800 CST
  • Martin Hope
    Cbhihe Altere o editor padrão para vim para _ sudo systemctl edit [unit-file] _ 2017-12-03 10:11:38 +0800 CST
  • Martin Hope
    showkey Como baixar o pacote não instalá-lo com o comando apt-get? 2017-12-03 02:15:02 +0800 CST
  • Martin Hope
    youxiao Por que os diretórios /home, /usr, /var, etc. têm o mesmo número de inode (2)? 2017-12-02 05:33:41 +0800 CST
  • Martin Hope
    user223600 gpg — o comando list-keys gera uid [ desconhecido ] depois de importar a chave privada para uma instalação limpa 2017-11-26 18:26:02 +0800 CST

Hot tag

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

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve