¿Herramienta no orientada a líneas para reemplazo de cadenas?

¿Herramienta no orientada a líneas para reemplazo de cadenas?

recientemente preguntéuna preguntasobre cómo eliminar el carácter de nueva línea si aparece después de otro carácter específico.

Las herramientas de procesamiento de texto de Unix son muy poderosas, pero casi todas manejan líneas de texto, lo cual está bien la mayor parte del tiempo cuando la entrada cabe en la memoria disponible.

Pero, ¿qué debo hacer si deseo reemplazar una secuencia de texto en un archivo enorme que no contiene nuevas líneas?

Por ejemplo, ¿reemplazar <foobar>con \n<foobar>sin leer la entrada línea por línea? (ya que solo hay una línea y tiene 2,5 G de caracteres).

Respuesta1

Lo primero que se me ocurre ante este tipo de problemas es cambiar el separador de registros. En la mayoría de las herramientas, esto está configurado \nde forma predeterminada, pero se puede cambiar. Por ejemplo:

  1. perla

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

    Explicación

    • -0: esto establece el separador de registros de entrada en un carácter dado suvalor hexadecimal. En este caso, lo estoy configurando >cuyo valor hexadecimal es 3E. El formato general es -0xHEX_VALUE. Esto es sólo un truco para dividir la línea en trozos manejables.
    • -pe: imprime cada línea de entrada después de aplicar el script proporcionado por -e.
    • s/<foobar>/\n$&/: una simple sustitución. En este caso, es $&lo que coincidió <foobar>.
  2. awk

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

    Explicación

    • RS="<": establezca el separador de registros de entrada en >.
    • gsub(/foobar>/,"\n<foobar>"): sustituir todos los casos de foobar>con \n<foobar>. Tenga en cuenta que debido a que RSse ha configurado en <, todos <se eliminan del archivo de entrada (así es como awkfunciona), por lo que debemos hacer coincidir foobar>(sin <) y reemplazar con \n<foobar>.
    • printf "%s",$0: imprime la "línea" actual después de la sustitución. $0es el registro actual, awkpor lo que contendrá lo que haya antes del <.

Los probé en un archivo de una sola línea de 2,3 GB creado con estos 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 el awkcomo el perlutilizaron cantidades insignificantes de memoria.

Respuesta2

gsar (búsqueda general y reemplazo)es una herramienta muy útil exactamente para este propósito.

La mayoría de las respuestas a esta pregunta utilizan herramientas basadas en registros y varios trucos para adaptarlas al problema, como cambiar el carácter separador de registros predeterminado a algo que se supone que ocurre con suficiente frecuencia en la entrada para no hacer que cada registro sea demasiado grande para manejarlo.

En muchos casos esto es muy fino e incluso legible. Me gustan los problemas que se pueden resolver de manera fácil y eficiente con herramientas disponibles en todas partes, como , awky Bourne Shell.trsed

Realizar una búsqueda binaria y reemplazar un archivo arbitrario enorme con contenido aleatorio no encaja muy bien con estas herramientas estándar de Unix.

Algunos de ustedes pueden pensar que esto es hacer trampa, pero no veo cómo usar la herramienta adecuada para el trabajo puede estar mal. En este caso se trata de un programa en C llamado gsarque tiene licencia bajoGPLv2, por lo que me sorprende bastante que no exista ningún paquete para esta herramienta tan útil en ninguno de los dos países.gentoo,sombrero rojo, niubuntu.

gsarutiliza una variante binaria delAlgoritmo de búsqueda de cadenas de Boyer-Moore.

El uso es sencillo:

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

donde -Fsignifica modo "filtro", es decir, lectura stdiny escritura stdout. También existen métodos para operar con archivos. -sespecifica la cadena de búsqueda y -rel reemplazo. La notación de dos puntos se puede utilizar para especificar valores de bytes arbitrarios.

Se admite el modo que no distingue entre mayúsculas y minúsculas ( -i), pero no se admiten expresiones regulares, ya que el algoritmo utiliza la longitud de la cadena de búsqueda para optimizar la búsqueda.

La herramienta también se puede utilizar sólo para realizar búsquedas, un poco como grep. gsar -bgenera los desplazamientos de bytes de la cadena de búsqueda coincidente e gsar -limprime el nombre del archivo y el número de coincidencias, si las hay, un poco como combinar grep -lcon wc.

La herramienta fue escrita porTormod Tjaberg(inicial) yHans Pedro Verne(mejoras).

Respuesta3

En el caso concreto en el que las cuerdas objetivo y de repuesto tienen la misma longitud,mapeo de memoriapuede venir al rescate. Esto es especialmente útil si el reemplazo debe realizarse en el lugar. Básicamente, estás asignando un archivo a la memoria virtual de un proceso, y el espacio de direcciones para el direccionamiento de 64 bits es enorme.Tenga en cuenta que el archivo no necesariamente se asigna a la memoria física de una sola vez., por lo que se pueden manejar archivos que tienen un tamaño varias veces mayor que la memoria física disponible en la máquina.

Aquí hay un ejemplo de Python que se reemplaza foobarconXXXXXX

#! /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)

Respuesta4

Awk opera en registros sucesivos. Puede utilizar cualquier carácter como separador de registros (excepto el byte nulo en muchas implementaciones). Algunas implementaciones admiten expresiones regulares arbitrarias (que no coinciden con la cadena vacía) como separador de registros, pero esto puede ser difícil de manejar porque el separador de registros se trunca desde el final de cada registro antes de guardarlo $0(GNU awk establece la variable RTen el separador de registros que fue eliminado del final del registro actual). Tenga en cuenta que printfinaliza su salida con el separador de registros de salida, ORSque es una nueva línea de forma predeterminada y se establece independientemente del separador de registros de entrada RS.

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

Puede seleccionar efectivamente un carácter diferente como separador de registros para otras herramientas ( sort,, sed...) intercambiando nuevas líneas con ese carácter con tr.

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

Muchas utilidades de texto GNU admiten el uso de un byte nulo en lugar de una nueva línea como separador.

información relacionada