Alternativa de Sed para buscar y reemplazar en líneas muy largas


9

Tengo archivos que fueron generados por un programa que no puso nuevas líneas al final de los registros. Quiero poner nuevas líneas entre los registros, y puedo hacerlo con un simple script sed:

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

El problema es que los archivos de entrada tienen un tamaño de varios gigabytes y, por lo tanto, las líneas de entrada a sed tienen varios GB de longitud. sed intenta mantener una línea en la memoria, lo que no funciona en este caso. Probé la --unbufferedopción, pero eso pareció hacerlo más lento y no permitió que terminara correctamente.


¿Sería posible cargar un archivo de entrada de ejemplo en algún lugar para que podamos probar algunas ideas?
mkc

3
Tal vez usted podría utilizar primero trpara traducir }en \ny luego usar sedpara agregar una }al final de cada línea? Así:tr '}' '\n' < your_file.txt| sed 's/$/}/'
user43791

¿Es útil agregar una nueva línea al final del archivo? Me gusta:printf "\n" >> file
niñera

1
@Ketan, supongo que escribir un archivo con 78 caracteres basura seguidos por }{repetidos hasta que sea suficiente con varios gigabytes.
niñera

@nanny - buen punto - pero ¿de dónde sacas 78? Si los registros ya están bloqueados, dd if=file cbs=80 conv=unblocklo haría, pero rara vez es así de simple.
mikeserv

Respuestas:


7

Puede usar otra herramienta que le permita configurar el separador de registro de entrada. Por ejemplo

  • Perl

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

    La variable especial $/es el separador de registro de entrada. Establecerlo en }{define líneas como terminando en }{. De esa manera, puede lograr lo que desea sin leer todo en la memoria.

  • mawk o gawk

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

    Esta es la misma idea. RS="}{"establece el separador de registros }{y luego imprime }, una nueva línea {(excepto el primer registro) y el registro actual.


3

Perl al rescate:

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

La configuración $/de \1024leerá el archivo en fragmentos de 1024 bytes. La $closingvariable maneja el caso cuando termina un fragmento }y comienza el siguiente {.


1
+1, probablemente la mejor solución; las otras soluciones perl / awk también funcionan bien, pero ¿qué pasa si el primer separador de registros se produce después de unos 17 GB de caracteres?
don_crissti

2

Deberías hacer:

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

Es probablemente la solución más eficiente.

Eso pone {}a proteger cualquier posible dato final. Con un trproceso más , puede intercambiarlo y hacer una línea en blanco en la cabecera del primer {campo. Me gusta...

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

Entonces, el primero, con los datos de ejemplo de don, hace:

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

... y el segundo sí ...

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

No hay una nueva línea final para el segundo ejemplo, aunque hay una para el primero.


0

Una sedutilidad binaria llamadabbe

Me resulta más fácil permanecer con sintaxis de tipo sed en este caso.

Yo mucho prefiero usar la bbeutilidad (disponible a través de su uni {,} Linu instalación del paquete de x, eq apt-get). O aquí, si eres uno de los git, aunque personalmente no he investigado ese enlace en particular.

1. Apoya el s/before/after/idioma

Es un "Editor de bloques binarios", que admite operaciones de tipo sed (entre otras). Esto incluye el s/before/after/idioma de sustitución súper común que necesitas. Tenga en cuenta que, dado que no hay líneas per se desde bbeel punto de vista, no hay una "g global" al final del comando.

Como prueba rápida (tenga en cuenta lo requerido -e):

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

produce:

he(replaced)(replaced)o

2. En el caso específico de }{que }\n{la conversión

Así que si tuviéramos un archivo masivo lleno de un millón de números en (por ejemplo) el formato {1}{2}{3}... {1000000}sin retornos de carro, podríamos cambiar el }{con }\n{facilidad, y tienen todos los números uno por línea.

Esto sería con este bbecomando:

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

Como se probó en este bucle zsh, que tomamos solo la cola de:

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

Lo que produciría esto:

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

(sin un retorno de carro posterior, por supuesto).

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.