Sed alternativa para pesquisar e substituir em linhas muito longas

9

Eu tenho arquivos que foram gerados por um programa que não colocou novas linhas no final dos registros. Quero colocar novas linhas entre os registros e posso fazê-lo com um simples script sed:

sed -e 's/}{/}\n{/g'

O problema é que os arquivos de entrada têm vários gigabytes de tamanho e, portanto, as linhas de entrada para sed têm vários GBs de comprimento. sed tenta manter uma linha na memória, o que não funciona nesse caso. Tentei a --unbufferedopção, mas isso pareceu torná-la mais lenta e não permitiu que ela terminasse corretamente.

Tom Panning
fonte
Seria possível fazer upload de um arquivo de entrada de exemplo em algum lugar para tentarmos algumas idéias?
Mkc 2/15
3
Talvez você poderia usar primeiro tra traduzir }em \ne, em seguida, usar sedpara adicionar um }no final de cada linha? Assim:tr '}' '\n' < your_file.txt| sed 's/$/}/'
user43791
A adição de uma nova linha no final do arquivo ajuda em tudo? Como:printf "\n" >> file
nanny
1
@ Ketan, eu suponho que escrever um arquivo com 78 caracteres ilegíveis seja }{repetido até que vários gigabytes sejam suficientes.
Nanny
@ nanny - bom ponto - mas onde você consegue 78? Se os registros já estiverem bloqueados, o dd if=file cbs=80 conv=unblockfaria - mas raramente é tão simples assim.
mikeserv

Respostas:

7

Você pode usar outra ferramenta que permite definir o separador de registros de entrada. Por exemplo

  • Perl

    perl -pe 'BEGIN{ $/="}{" } s/}{/}\n{/g' file
    

    A variável especial $/é o separador de registros de entrada. Configurá-lo para }{definir linhas como terminando em }{. Dessa forma, você pode conseguir o que deseja sem ler a coisa toda na memória.

  • mawk ou gawk

    awk -v RS="}{" -vORS= 'NR > 1 {print "}\n{"}; {print}' file 
    

    Essa é a mesma ideia. RS="}{"define o separador de registro para }{e, em seguida, você imprime }uma nova linha {(exceto o primeiro registro) e o registro atual.

terdon
fonte
3

Perl para o resgate:

perl -i~ -e ' $/ = \1024;
              while (<>) {
                  print "\n" if $closing and /^{/;
                  undef $closing;
                  s/}{/}\n{/g;
                  print;
                  $closing = 1 if /}$/;
              } ' input1 input2

A configuração $/para \1024lerá o arquivo em pedaços de 1024 bytes. A $closingvariável lida com o caso quando um pedaço termina }e o próximo começa com {.

choroba
fonte
1
+1, provavelmente a melhor solução; as outras soluções perl / awk também funcionam bem, mas e se o primeiro separador de registros ocorrer após cerca de 17 GB de caracteres?
don_crissti
2

Você deveria fazer:

{ <infile tr \} \\n;echo {; } | paste -d'}\n' - /dev/null >outfile

Provavelmente é a solução mais eficiente.

Isso coloca um {}para proteger todos os possíveis dados finais. Com mais um trprocesso, você pode trocá-lo e fazer uma linha em branco no início do primeiro {campo. Gostar...

tr {} '}\n'| paste -d{\\0 /dev/null - | tr {}\\n \\n{}

Portanto, o primeiro, com dados de exemplo de don, faz:

printf '{one}{two}{three}{four}' |
{ tr \} \\n; echo {; }           |
paste -d'}\n' - /dev/null
{one}
{two}
{three}
{four}
{}

... e o segundo faz ...

printf '{one}{two}{three}{four}'      |
tr {} '}\n'| paste -d{\\0 /dev/null - |
tr {}\\n \\n{}
#leading blank
{one}
{two}
{three}
{four}

Não há nova linha final no segundo exemplo - embora exista uma no primeiro.

mikeserv
fonte
0

Um sedutilitário tipo binário chamadobbe

Acho mais fácil permanecer com a sintaxe semelhante a sed neste caso.

I muito preferem usar o bbeutilitário (disponível através do seu {uni, Linu} instalação do pacote de x, eq apt-get). Ou aqui, se você faz parte da galera do idiota, apesar de eu não ter pessoalmente verificado esse link em particular.

1. Suporta o s/before/after/idioma

É um "Editor de Blocos Binários", que suporta operações semelhantes a sed (entre outras). Isso inclui o s/before/after/idioma de substituição super comum que você precisa. Observe que, como não existem linhas em si do bbeponto de vista, não há "g global" no final do comando.

Como um teste rápido (observe o necessário -e):

$ echo hello | bbe -e 's/l/(replaced)/'

produz:

he(replaced)(replaced)o

2. No seu caso específico }{da }\n{conversão

Portanto, se tivéssemos um arquivo enorme preenchido com um milhão de números no formato (digamos) {1}{2}{3}... {1000000}sem retorno de carro, poderíamos trocá }{-lo }\n{facilmente e ter todos os números um por linha.

Isso seria com este bbecomando:

bbe -e 's/}{/}\n{/'

Conforme testado neste loop zsh, do qual pegamos apenas o final:

$ for ((num=0; num<1000000; num++)) do; echo -n "{$num}"; done | bbe -e 's/}{/}\n{/' | tail

O que produziria isso:

{999990}
{999991}
{999992}
{999993}
{999994}
{999995}
{999996}
{999997}
{999998}
{999999}

(sem retorno de carro à direita, é claro.)

tgm1024 - Monica foi maltratada
fonte