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).

MattBianco
fonte
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.

terdon
fonte
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).

MattBianco
fonte
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)
iruvar
fonte
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
mikeserv
fonte
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.

Gilles 'SO- parar de ser mau'
fonte