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.new
tem 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 sed
em cada um deles e depois mesclá-los com cat
? Isso faz sentido? Existe uma solução mais elegante?
Para um arquivo tão grande, uma possibilidade é o Flex. Seja
unk.l
:Em seguida, compile e execute:
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.tr
processa bytes, não linhas, então não se importa com nenhum tamanho de registro. Supondo que;
funcione: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 sedsed '2,$ s/…
para evitar uma correspondência espúria.Como alternativa, use o último caractere.
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.
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") :).
Existe um
replace
utilitário no pacote mariadb-server/mysql-server. Ele substitui strings simples (não expressões regulares) e, ao contrário de grep/sed/awkreplace
, não se preocupa com\n
and\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
replace
o , ele é empacotado dessa forma apenas no Fedora. Outras distros/sistemas operacionais podem tê-lo empacotado separadamente.Acho que a versão C pode ter um desempenho muito melhor:
EDIT: Modificado de acordo com as sugestões dos comentários. Também corrigido bug com o padrão
<<unk>
.O GNU
grep
pode mostrar a você o deslocamento de correspondências em arquivos "binários", sem ter que ler linhas inteiras na memória. Você pode usardd
para ler até esse deslocamento, pular a correspondência e continuar copiando do arquivo.Para velocidade, eu dividi
dd
em 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. Agrep
saída é, por exemplo,13977:<unk>
, e isso é dividido nos dois pontos pela leitura em variáveisoffset
epattern
. Temos que acompanharpos
quantos bytes já foram copiados do arquivo.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.
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:
Você precisará adicionar uma nova linha no final do arquivo, se houver, porque o comando tr irá removê-la.
Usando
perl
Gerenciando seus próprios buffers
Você pode usar
IO::Handle
parasetvbuf
gerenciar os buffers padrão ou pode gerenciar seus próprios buffers comsysread
esyswrite
. Verifiqueperldoc -f sysread
eperldoc -f syswrite
para 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.
Se você está indo para este caminho
<unk>
e<raw_unk>
são do mesmo tamanho de byte.CHUNKSIZE
limite, se estiver substituindo mais de 1 byte.Você pode tentar o bbe ( editor de blocos binários ), um "
sed
para arquivos binários".Tive um bom sucesso ao usá-lo em um arquivo de texto de 7 GB sem
EOL
caracteres, 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.Aqui está um pequeno programa Go que executa a tarefa (
unk.go
):Basta construí-lo
go build unk.go
e 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.