ferramenta não orientada a linha para substituição de string?


13

Recentemente, fiz uma pergunta sobre como remover o caractere de nova linha se ele ocorrer após outro caractere específico.

As ferramentas de processamento de texto Unix são muito poderosas, mas quase todas elas lidam com linhas de texto, o que é bom na maioria das vezes quando a entrada se encaixa na memória disponível.

Mas o que devo fazer se desejar substituir uma sequência de texto em um arquivo enorme que não contenha novas linhas?

Por exemplo, substitua <foobar>por \n<foobar>sem ler a entrada linha por linha? (uma vez que existe apenas uma linha e tem 2,5 G de comprimento).


1
Você está aberto a usar perlou python?
— Iruvar

Perl está bem. Acabei de encontrar gsar( home.online.no/~tjaberg ) que tentarei.
— Matt Bianco

Respostas:


12

A primeira coisa que me ocorre ao enfrentar esse tipo de problema é alterar o separador de registros. Na maioria das ferramentas, isso é definido como \npadrão, mas pode ser alterado. Por exemplo:

  1. Perl

    perl -0x3E -pe 's/<foobar>/\n$&/' file
    

    Explicação

    • -0: define o separador de registros de entrada para um caractere, devido ao seu valor hexadecimal . Nesse caso, estou configurando-o para >cujo valor hexadecimal é 3E. O formato geral é -0xHEX_VALUE. Este é apenas um truque para quebrar a linha em pedaços gerenciáveis.
    • -pe: imprime cada linha de entrada após aplicar o script fornecido por -e.
    • s/<foobar>/\n$&/: uma simples substituição. O $&é o que foi correspondido, neste caso <foobar>.
  2. awk

    awk '{gsub(/foobar>/,"\n<foobar>");printf "%s",$0};' RS="<" file
    

    Explicação

    • RS="<": defina o separador de registros de entrada como >.
    • gsub(/foobar>/,"\n<foobar>"): substitua todos os casos de foobar>com \n<foobar>. Observe que, como RSfoi definido como <, todos <são removidos do arquivo de entrada (é assim que awkfunciona); portanto, precisamos corresponder foobar>(sem a <) e substituir por \n<foobar>.
    • printf "%s",$0: imprime a "linha" atual após a substituição. $0é o registro atual, awkentão ele conterá o que estava antes do <.

Testei-os em um arquivo de linha única de 2,3 GB criado com estes comandos:

for i in {1..900000}; do printf "blah blah <foobar>blah blah"; done > file
for i in {1..100}; do cat file >> file1; done
mv file1 file

Tanto a quantidade awkquanto a perlquantidade negligenciável de memória usada.


Você já experimentou Tie::File perldoc.perl.org/Tie/File.html . Eu acho que são as melhores características Perlao lidar com arquivos enormes.
— cuonglm

@Gnouc Eu brinquei um pouco com isso, sim. Mas i) o OP já manifestou antipatia pelo Perl em outra pergunta, então eu queria mantê-lo simples ii) tendem a evitar o uso de módulos externos, a menos que seja absolutamente necessário e iii) o uso do módulo Tie :: File tornaria a sintaxe consideravelmente menor Claro.
— terdon

Aceita. Uma pequena nota que Tie::Fileé um módulo básico desde então v5.7.3.
— cuonglm

9

O gsar (pesquisa geral e substituição) é uma ferramenta muito útil exatamente para esse fim.

A maioria das respostas a essa pergunta usa ferramentas baseadas em registros e vários truques para adaptá-las ao problema, como alternar o caractere separador de registros padrão para algo que se supõe estar ocorrendo com freqüência suficiente na entrada para não tornar cada registro muito grande para lidar.

Em muitos casos, isso é muito bom e até legível. Eu gosto de problemas que podem ser facilmente / eficientemente resolvidos com ferramentas em todos os lugares-disponíveis, tais como awk, tr, sede o shell Bourne.

A realização de uma pesquisa binária e a substituição em um arquivo enorme arbitrário com conteúdo aleatório não se encaixa muito bem nessas ferramentas unix padrão.

Alguns de vocês podem pensar que isso é trapaça, mas não vejo como o uso da ferramenta certa para o trabalho possa estar errado. Nesse caso, é um programa C chamado gsarlicenciado sob a GPL v2 , por isso me surpreende bastante que não exista um pacote para essa ferramenta muito útil no gentoo , redhat ou no ubuntu .

gsarusa uma variante binária do algoritmo de pesquisa de string de Boyer-Moore .

O uso é direto:

gsar -F '-s<foobar>' '-r:x0A<foobar>'

onde -Fsignifica modo "filtro", ou seja, leia a stdingravação para stdout. Existem métodos para operar em arquivos também. -sespecifica a cadeia de pesquisa e -ra substituição. A notação de dois pontos pode ser usada para especificar valores de bytes arbitrários.

O modo que não diferencia maiúsculas de minúsculas é suportado ( -i), mas não há suporte para expressões regulares, pois o algoritmo usa o comprimento da cadeia de pesquisa para otimizar a pesquisa.

A ferramenta também pode ser usada apenas para pesquisa, um pouco como grep. gsar -boutputs os deslocamentos byte da cadeia de pesquisa correspondida, e gsar -limpressões nome do arquivo e número de partidas, se houver, um pouco como combinar grep -lcom wc.

A ferramenta foi escrita por Tormod Tjaberg (inicial) e Hans Peter Verne (melhorias).


Se é GPL que você considera o seu empacotamento para uma distro :)
— Rqomey

1
Na verdade, estou pensando seriamente em fazer um eento para o gentoo. Talvez uma rpm também. Mas eu nunca criei um pacote .deb antes, então espero que alguém me supere (porque isso levará algum tempo).
— Matt Bianco

Duvido que isso seja muito consolador, mas o homebrew do OS X tem a fórmula gsar.
— crazysim

5

No caso restrito em que as seqüências de destino e de substituição têm o mesmo comprimento, o mapeamento de memória pode ser útil. Isso é especialmente útil se a substituição precisar ser realizada no local. Você está basicamente mapeando um arquivo na memória virtual de um processo, e o espaço de endereço para o endereçamento de 64 bits é enorme. Observe que o arquivo não é necessariamente mapeado para a memória física de uma só vez , para que arquivos com várias vezes o tamanho da memória física disponível na máquina possam ser tratados.

Aqui está um exemplo de Python que substitui foobarporXXXXXX

#! /usr/bin/python
import mmap
import contextlib   
with open('test.file', 'r+') as f:
 with contextlib.closing(mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_WRITE)) as m:
   pos = 0
   pos = m.find('foobar', pos)
   while pos > 0:
    m[pos: pos+len('XXXXXX')] = 'XXXXXX'
    pos = m.find('foobar', pos)

4

Existem muitas ferramentas para isso:

ddé o que você deseja usar se desejar bloquear um arquivo - leia com confiabilidade apenas um certo número de bytes apenas um certo número de vezes. Ele lida de maneira portável com o bloqueio e desbloqueio de fluxos de arquivos:

tr -dc '[:graph:]' </dev/urandom | dd bs=32 count=1 cbs=8 conv=unblock,sync 2>/dev/null

###OUTPUT###

UI(#Q5\e BKX2?A:Z RAxGm:qv t!;/v!)N

Eu também uso tracima porque ele pode manipular a conversão de qualquer byte ASCII para qualquer outro (ou, nesse caso, excluir qualquer byte ASCII que não seja um caractere imprimível que não seja espaço). É o que eu usei em resposta à sua outra pergunta esta manhã, de fato, quando eu fiz:

tr '>\n' '\n>' | sed 's/^>*//' | tr '\n>' '>\n' 

Existem muitos similares . Essa lista deve fornecer um subconjunto de denominador comum mais baixo com o qual você possa se familiarizar.

Mas, se eu fosse fazer o processamento de texto em 2,5 gbs de arquivo binário, eu poderia começar od. Pode fornecer a você um octal dumpou vários outros formatos. Você pode especificar todos os tipos de opções - mas vou fazer um byte por linha em um \Cformato de escape:

Os dados que você obterá odserão regulares no intervalo que você especificar - como mostramos abaixo. Mas primeiro - aqui está uma resposta para sua pergunta:

printf 'first\nnewline\ttab spacefoobar\0null' |
od -A n -t c -v -w1 |
sed 's/^ \{1,3\}//;s/\\$/&&/;/ /bd
     /\\[0nt]/!{H;$!d};{:d
    x;s/\n//g}'

Esse pouco acima delimita \nlinhas ew, \0nulos, \tabs e <spaces>preserva a \Cstring de escape para o delimitador. Observe as funções He xusadas - sempre que sedencontra um delimitador, ele troca o conteúdo de seus buffers de memória. Dessa maneira, sedapenas retém o máximo de informações necessárias para delimitar o arquivo com segurança e não sucumbe às excedências de buffer - não é, desde que encontre seus delimitadores. Enquanto isso, sedcontinuará processando sua entrada e odcontinuará fornecendo-a até encontrar EOF.

Como é, sua saída é assim:

first
\nnewline
\ttab
 spacefoobar
\0null

Então, se eu quiser foobar:

printf ... | od ... | sed ... | 
sed 's/foobar/\
&\
/g'

###OUTPUT###

first
\nnewline
\ttab
 space
foobar

\0null

Agora, se você quiser usar os Cescapes, é muito fácil - porque a barra invertida seddupla já \\escapou de todas as barras invertidas de entrada única, portanto, a printfexecução de xargsnão terá problemas para produzir a saída conforme sua especificação. Mas como xargs aspas shell, você precisará citá-las novamente:

printf 'nl\ntab\tspace foobarfoobar\0null' |
PIPELINE |
sed 's/./\\&/g' | 
xargs printf %b | 
cat -A

###OUTPUT###

nl$
tab^Ispace $
foobar$
$
foobar$
^@null%

Isso poderia ter sido facilmente salvo em uma variável do shell e produzido posteriormente de maneira idêntica. O último sedinsere uma \barra invertida antes de cada caractere em sua entrada, e isso é tudo.

E aqui está o que tudo parece antes de sedse apossar:

printf 'nl\ntab\tspace foobarfoobar\0null' |
od -A n -t c -v -w1

   n
   l
  \n
   t
   a
   b
  \t
   s
   p
   a
   c
   e

   f
   o
   o
   b
   a
   r
   f
   o
   o
   b
   a
   r
  \0
   n
   u
   l
   l

2

O Awk opera em registros sucessivos. Ele pode usar qualquer caractere como separador de registros (exceto o byte nulo em muitas implementações). Algumas implementações suportam expressões regulares arbitrárias (que não correspondem à cadeia vazia) como separador de registros, mas isso pode ser complicado porque o separador de registros é truncado no final de cada registro antes de ser armazenado $0(o GNU awk define a variável RTcomo separador de registros que foi retirado do final do registro atual). Observe que printfinaliza sua saída com o separador de registros de saída, ORSque é uma nova linha por padrão e configurada independentemente do separador de registros de entrada RS.

awk -v RS=, 'NR==1 {printf "input up to the first comma: %s\n", $0}'

Você pode efetivamente selecionar um personagem diferente como o separador de registro para outras ferramentas ( sort, sed...), trocando novas linhas com aquele personagem com tr.

tr '\n,' ',\n' |
sed 's/foo/bar/' |
sort |
tr '\n,' ',\n'

Muitos utilitários de texto GNU suportam o uso de um byte nulo em vez de uma nova linha como separador.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.