herramienta no orientada a líneas para el reemplazo de cuerdas?

13

Recientemente hice una pregunta sobre cómo eliminar el carácter de nueva línea si ocurre después de otro carácter específico.

Las herramientas de procesamiento de texto de Unix son muy potentes, pero casi todas tratan con 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 líneas nuevas?

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

MattBianco
fuente
1
¿Estás abierto a usar perlo python?
iruvar
Perl está bien. Acabo de encontrar gsar( home.online.no/~tjaberg ) que intentaré.
MattBianco

Respuestas:

12

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

  1. Perl

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

    Explicación

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

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

    Explicación

    • RS="<": establece el separador de registro de entrada en >.
    • gsub(/foobar>/,"\n<foobar>"): sustituye todos los casos de foobar>con \n<foobar>. Tenga en cuenta que debido a que RSse ha establecido en <, todos <se eliminan del archivo de entrada (así es como awkfunciona), por lo que debemos hacer coincidir foobar>(sin a <) 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 estaba antes de <.

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 awky las perlcantidades insignificantes usadas de memoria.

terdon
fuente
¿Alguna vez has probado Tie::File perldoc.perl.org/Tie/File.html . Creo que son las mejores características Perlcuando se trata de archivos enormes.
Cuonglm
@Gnouc He jugado un poco con eso, sí. Pero i) el OP ya ha manifestado su disgusto por Perl en otra pregunta, así que quería mantenerlo simple ii) Tiendo a evitar el uso de módulos externos a menos que sea absolutamente necesario y iii) El uso del módulo Tie :: File haría que la sintaxis sea considerablemente menor claro.
terdon
De acuerdo. Una pequeña nota que Tie::Filees un módulo central desde entonces v5.7.3.
Cuonglm
9

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

La mayoría de las respuestas a esta pregunta utilizan herramientas basadas en registros y varios trucos para que se adapten 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 bueno e incluso legible. Me gustan los problemas que pueden ser fácilmente resueltos / eficientemente con las herramientas disponibles en todas partes, tales como awk, tr, sedy el shell Bourne.

Realizar una búsqueda binaria y reemplazarlo en un archivo enorme arbitrario con contenido aleatorio no se ajusta muy bien a estas herramientas estándar de Unix.

Algunos de ustedes pueden pensar que esto es hacer trampa, pero no veo cómo puede estar mal usar la herramienta adecuada para el trabajo. En este caso, se trata de un programa llamado C gsarque está licenciado bajo GPL v2 , por lo que me sorprende bastante que no haya un paquete para esta herramienta tan útil ni en gentoo , redhat ni ubuntu .

gsarutiliza una variante binaria del algoritmo 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, stdinescritura stdout. También hay métodos para operar en archivos. -sespecifica la cadena de búsqueda y -rel reemplazo. La notación de dos puntos se puede usar 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 usar solo para buscar, un poco como grep. gsar -bgenera los desplazamientos de bytes de la cadena de búsqueda coincidente e gsar -limprime el nombre de archivo y el número de coincidencias, si corresponde, un poco como combinar grep -lcon wc.

La herramienta fue escrita por Tormod Tjaberg (inicial) y Hans Peter Verne (mejoras).

MattBianco
fuente
Si es GPL, ¿considerarías
empacarlo
1
De hecho, estoy pensando seriamente en hacer un ebuild gentoo para ello. Tal vez un rpm también. Pero nunca antes he creado un paquete .deb, así que espero que alguien me supere (porque me llevará algún tiempo).
MattBianco
Dudo que esto sea un gran consuelo, pero el homebrew de OS X tiene la fórmula para gsar.
crazysim
5

En el caso estrecho donde las cadenas de destino y de reemplazo son de la misma longitud, el mapeo de memoria puede venir al rescate. Esto es especialmente útil si el reemplazo debe realizarse en el lugar. Básicamente, está 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 se asigna necesariamente a la memoria física de una vez , por lo que se pueden tratar archivos que tienen varias veces el tamaño de la memoria física disponible en la máquina.

Aquí hay un ejemplo de Python que 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)
iruvar
fuente
4

Hay muchas herramientas para esto:

ddes lo que desea usar si desea bloquear un archivo: lea de manera confiable solo un cierto número de bytes solo un cierto número de veces. Maneja de forma portátil el bloqueo y desbloqueo de secuencias de archivos:

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

También lo uso trarriba porque puede manejar la conversión de cualquier byte ASCII a cualquier otro (o, en este caso, eliminar cualquier byte ASCII que no sea un carácter imprimible sin espacio). Es lo que usé en respuesta a su otra pregunta esta mañana, de hecho, cuando lo hice:

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

Hay muchos parecidos . Esa lista debe proporcionar un subconjunto de denominador común más bajo con el que pueda familiarizarse.

Pero, si fuera a hacer procesamiento de texto en 2.5 gbs de archivo binario, podría comenzar con od. Puede darle uno octal dumpo cualquiera de varios otros formatos. Puede especificar todo tipo de opciones, pero solo haré un byte por línea en un \Cformato de escape:

Los datos que obtendrá odserán regulares en cualquier intervalo que especifique, como se muestra a continuación. Pero primero, aquí hay una respuesta a su pregunta:

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}'

Ese poco más arriba delimita en \nlíneas de tensión, \0nulos, \tabdominales y al <spaces>tiempo que conserva la \Ccadena de escape para el delimitador. Tenga en cuenta las funciones Hy xutilizadas: cada vez que sedencuentra un delimitador, intercambia el contenido de sus memorias intermedias. De esta manera, sedsolo retiene tanta información como debe para delimitar de manera confiable el archivo y no sucumbe a desbordamientos de búfer, es decir, no, siempre y cuando realmente encuentre sus delimitadores. Mientras lo haga, sedcontinuará procesando su entrada y odcontinuará proporcionándola hasta que se encuentre EOF.

Como es, su salida se ve así:

first
\nnewline
\ttab
 spacefoobar
\0null

Entonces si quiero foobar:

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

###OUTPUT###

first
\nnewline
\ttab
 space
foobar

\0null

Ahora, si desea hacer uso de los Cescapes, es bastante fácil, sedya que la \\barra invertida doble escapó de todas sus barras invertidas de entrada individuales, por lo que si se printfejecuta desde allí xargsno tendrá problemas para producir el resultado según sus especificaciones. Pero xargs come comillas, así que tendrás que volver a comillas dobles:

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

###OUTPUT###

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

Eso podría haberse guardado tan fácilmente en una variable de shell y salir más tarde de manera idéntica. El último sedinserta una \barra diagonal inversa antes de cada carácter en su entrada, y eso es todo.

Y así es como se ve todo antes de que lo sedalcance:

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
fuente
2

Awk opera en sucesivos registros. Puede usar 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 se eliminó del final del registro actual). Tenga en cuenta que printtermina su salida con el separador de registro de salida, ORSque es una nueva línea de forma predeterminada y se establece independientemente del separador de registro 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 el separador de registros para otras herramientas ( sort, sed, ...) mediante el canje de los saltos de línea con ese personaje 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.

Gilles 'SO- deja de ser malvado'
fuente