Tengo un archivo de texto enorme (70 GB), una línea y quiero reemplazar una cadena (token). Quiero reemplazar el token <unk>
, con otro token ficticio ( problema de guantes ).
Lo intenté sed
:
sed 's/<unk>/<raw_unk>/g' < corpus.txt > corpus.txt.new
¡pero el archivo de salida corpus.txt.new
tiene cero bytes!
También intenté usar perl:
perl -pe 's/<unk>/<raw_unk>/g' < corpus.txt > corpus.txt.new
pero recibí un error de falta de memoria.
Para archivos más pequeños, funcionan los dos comandos anteriores.
¿Cómo puedo reemplazar una cadena es un archivo? Esta es una pregunta relacionada, pero ninguna de las respuestas funcionó para mí.
Editar : ¿Qué hay de dividir el archivo en trozos de 10 GB (o lo que sea) cada uno y aplicarlo sed
en cada uno de ellos y luego fusionarlos cat
? ¿Tiene sentido? ¿Hay una solución más elegante?
text-processing
sed
large-files
Christos Baziotis
fuente
fuente
split
con la-b
opción que define los tamaños de archivo de fragmentos en bytes. Procese cada uno a su vez usandosed
y el remontaje Existe el riesgo de que<unk>
se pueda dividir en dos archivos y no se encuentre ...Respuestas:
Las herramientas habituales de procesamiento de texto no están diseñadas para manejar líneas que no caben en la RAM. Tienden a funcionar leyendo un registro (una línea), manipulándolo y generando el resultado, luego continúan con el siguiente registro (línea).
Si hay un carácter ASCII que aparece con frecuencia en el archivo y no aparece en
<unk>
o<raw_unk>
, puede usarlo como separador de registros. Como la mayoría de las herramientas no permiten separadores de registros personalizados, cambie entre ese carácter y las nuevas líneas.tr
procesa bytes, no líneas, por lo que no le importa ningún tamaño de registro. Suponiendo que;
funciona:También puede anclar en el primer carácter del texto que está buscando, suponiendo que no se repita en el texto de búsqueda y que aparezca con suficiente frecuencia. Si el archivo puede comenzar con
unk>
, cambie el comando sedsed '2,$ s/…
para evitar una coincidencia espuria.Alternativamente, use el último personaje.
Tenga en cuenta que esta técnica supone que sed opera sin problemas en un archivo que no termina con una nueva línea, es decir, que procesa la última línea parcial sin truncarla y sin agregar una nueva línea final. Funciona con GNU sed. Si puede elegir el último carácter del archivo como separador de registros, evitará cualquier problema de portabilidad.
fuente
awk -v RS=, -v ORS=, '{gsub(/<unk>/, "<raw_unk>"); print}'
¿No?-0
y el valor octal de un carácter, o dentro del script se puede establecer con una variable especial$/
awk
evite pasar el flujo dos veces atr
. Entonces, ¿sería aún más lento?tr
es muy rápido y la tubería incluso se puede paralelizar.Para un archivo tan grande, una posibilidad es Flex. Dejar
unk.l
ser:Luego compila y ejecuta:
fuente
make
tiene reglas predeterminadas para esto, en lugar de flex / cc puede agregar un%option main
como la primera línea de unk.l y luego simplementemake unk
. Yo uso más o menos reflexivamente%option main 8bit fast
, y tengoexport CFLAGS='-march=native -pipe -Os'
en mi.bashrc
.%option main
+make
+ opcionalmenteCFLAGS
es un truco muy bueno! ¿Es-march=native
el comportamiento predeterminado?Por lo tanto, no tiene suficiente memoria física (RAM) para contener todo el archivo a la vez, pero en un sistema de 64 bits tiene suficiente espacio de direcciones virtuales para mapear todo el archivo. Las asignaciones virtuales pueden ser útiles como un simple hack en casos como este.
Todas las operaciones necesarias están incluidas en Python. Hay varias sutilezas molestas, pero evita tener que escribir código C. En particular, se debe tener cuidado para evitar copiar el archivo en la memoria, lo que anularía completamente el punto. En el lado positivo, obtienes informes de errores de forma gratuita ("excepciones" de Python) :).
fuente
search
puede contener un carácter NUL. Y noto que la otra versión de C aquí no admite caracteres NULreplace
). Le invitamos a obtener la versión C para fines de comparación. Sin embargo, recuerde que mi versión incluye informes básicos de errores para las operaciones que realiza. La versión C al menos sería más molesta para leer IMO, cuando se incluye el informe de errores.Hay una
replace
utilidad en el paquete mariadb-server / mysql-server. Reemplaza cadenas simples (no expresiones regulares) y, a diferencia de grep / sed / awk,replace
no le importa\n
y\0
. El consumo de memoria es constante con cualquier archivo de entrada (aproximadamente 400 kb en mi máquina).Por supuesto, no necesita ejecutar un servidor mysql para usarlo
replace
, solo está empaquetado de esa manera en Fedora. Otras distribuciones / sistemas operativos pueden tenerlo empaquetado por separado.fuente
Creo que la versión C podría funcionar mucho mejor:
EDITAR: modificado de acuerdo con las sugerencias de los comentarios. También se corrigió un error con el patrón
<<unk>
.fuente
memcpy
la velocidad (es decir, el cuello de botella de memoria) es algo así como 12 GB / segundo en una CPU x86 reciente (por ejemplo, Skylake). Incluso con la sobrecarga de llamadas al sistema stdio +, para un archivo de 30 MB en caliente en caché de disco, esperaría quizás 1 GB / segundo para una implementación eficiente. ¿Compiló con la optimización deshabilitada o la E / S de un solo carácter es realmente tan lenta?getchar_unlocked
/putchar_unlocked
podría ayudar, pero definitivamente es mejor leer / escribir en fragmentos de quizás 128 kB (la mitad del tamaño de caché L2 en la mayoría de las CPU x86, por lo que en su mayoría golpeó en L2 mientras realizaba un bucle después de la lectura)fix
programa"<<unk>"
todavía no funciona sipattern
comienza con una secuencia repetida de caracteres (es decir, no funcionaría si intentara reemplazar el oso hormiguero con cebra y tuviera información de aaardvak, o si estuviera tratando de reemplazar ababc y tuvo aporte de abababc). En general, no puede avanzar por el número de caracteres que ha leído a menos que sepa que no hay posibilidad de que una coincidencia comience en los caracteres que ha leído.GNU
grep
puede mostrarle el desplazamiento de coincidencias en archivos "binarios", sin tener que leer líneas completas en la memoria. Luego puede usardd
para leer hasta este desplazamiento, omitir la coincidencia y luego continuar copiando del archivo.En cuanto a la velocidad, la he dividido
dd
en una gran lectura de tamaño de bloque 1048576 y una lectura más pequeña de 1 byte a la vez, pero esta operación aún será un poco lenta en un archivo tan grande. Elgrep
resultado es, por ejemplo,13977:<unk>
y esto se divide en los dos puntos por la lectura en variablesoffset
ypattern
. Tenemos que hacer un seguimientopos
de cuántos bytes ya se han copiado del archivo.fuente
Aquí hay otra línea de comando UNIX única que podría funcionar mejor que otras opciones, porque puede "buscar" un "tamaño de bloque" que funcione bien. Para que esto sea robusto, debe saber que tiene al menos un espacio en cada X caracteres, donde X es su "tamaño de bloque" arbitrario. En el siguiente ejemplo, he elegido un "tamaño de bloque" de 1024 caracteres.
Aquí, el plegado capturará hasta 1024 bytes, pero el -s se asegura de que se rompa en un espacio si hay al menos uno desde el último salto.
El comando sed es tuyo y hace lo que esperas.
Luego, el comando tr "desplegará" el archivo convirtiendo las nuevas líneas que se insertaron de nuevo en nada.
Debería considerar probar tamaños de bloque más grandes para ver si funciona más rápido. En lugar de 1024, puede probar 10240 y 102400 y 1048576 para la opción -w de plegar.
Aquí hay un ejemplo desglosado por cada paso que convierte todas las N en minúsculas:
Tendrá que agregar una nueva línea al final del archivo si tiene una, porque el comando tr lo eliminará.
fuente
Utilizando
perl
Administrar tus propios buffers
Puede usar
IO::Handle
'ssetvbuf
para administrar las memorias intermedias predeterminadas, o puede administrar sus propias memorias intermedias consysread
ysyswrite
. Compruebeperldoc -f sysread
yperldoc -f syswrite
para obtener más información, esencialmente omiten io almacenado en el búfer.Aquí hacemos rodar nuestro propio buffer IO, pero lo hacemos de forma manual y arbitraria en 1024 bytes. También abrimos el archivo para RW, así que lo hacemos todo en el mismo FH a la vez.
Si vas a ir por esta ruta
<unk>
y<raw_unk>
son el mismo tamaño en bytes.CHUNKSIZE
límite, si está reemplazando más de 1 byte.fuente
<unk>
cae en un límite entre trozos?Puede probar bbe ( editor de bloques binarios ), un "
sed
para archivos binarios".Tuve un buen éxito al usarlo en un archivo de texto de 7GB sin
EOL
caracteres, reemplazando múltiples ocurrencias de una cadena con una de diferente longitud. Sin intentar ninguna optimización, dio un rendimiento de procesamiento promedio de> 50 MB / s.fuente
Con
perl
, podría trabajar con registros de longitud fija como:Y espero que no haya
<unk>
más de dos de esos registros de 100 MB.fuente
while read -N 1000 chunk;
(el1000
elegido como ejemplo). La solución para el<unk>
, dividido entre los fragmentos, es dos pasos a través del archivo: el primero con los fragmentos de 100 MB y el segundo con los fragmentos de '100 MB + 5 bytes'. Pero no es la solución óptima en el caso del archivo de 70 GB.<unk>
.<unk>
ocurrencias estén muy lejos, si no, use$/ = ">"
ys/<unk>\z/<raw_unk>/g
) de ser correcto.Aquí hay un pequeño programa Go que realiza la tarea (
unk.go
):Simplemente compílalo
go build unk.go
y ejecútalo como./unk <input >output
.EDITAR:
Lo siento, no leí que todo está en una línea, así que intenté leer el archivo carácter por carácter ahora.
EDITAR II:
Se aplicó la misma corrección que al programa C.
fuente
scanner.Split(bufio.ScanRunes)
hace la magiago doc bufio.MaxScanTokenSize
el tamaño predeterminado del búfer.C
programa, esto no funciona para reemplazar el oso hormiguero por cebra con una entrada de oso hormiguero.Esto puede ser excesivo para un archivo de 70GB y una simple búsqueda y reemplazo, pero el marco Hadoop MapReduce resolvería su problema en este momento sin costo alguno (elija la opción 'Nodo único' cuando lo configure para ejecutarlo localmente), y puede ser escalado a capacidad infinita en el futuro sin la necesidad de modificar su código.
El tutorial oficial en https://hadoop.apache.org/docs/stable/hadoop-mapreduce-client/hadoop-mapreduce-client-core/MapReduceTutorial.html usa Java (extremadamente simple) pero puede encontrar bibliotecas de clientes para Perl o cualquier idioma que quieras usar.
Entonces, si más adelante descubre que está realizando operaciones más complejas en archivos de texto de 7000 GB, y que tiene que hacer esto 100 veces al día, puede distribuir la carga de trabajo en varios nodos que aprovisiona o que se aprovisionan automáticamente para usted en la nube. basado en clúster Hadoop.
fuente
Todas las sugerencias anteriores requieren leer el archivo completo y escribir todo el archivo. Esto no solo lleva mucho tiempo, sino que también requiere 70 GB de espacio libre.
1) Si entiendo su caso específico correctamente, ¿sería aceptable reemplazar con alguna otra cadena de la MISMA longitud?
2a) ¿Hay múltiples ocurrencias? 2b) Si es así, ¿sabes cuántos?
Estoy seguro de que ya resolvió este problema de más de un año y me gustaría saber qué solución utilizó.
Propondría una solución (muy probablemente en C) que leería los BLOQUES del archivo buscando cada cadena para tener en cuenta el posible cruce de bloques. Una vez encontrado, reemplace la cadena con la MISMA longitud alternativa y escriba solo ese BLOQUE. Continuando por el número conocido de ocurrencias o hasta el final del archivo. Esto requeriría tan pocas escrituras de número de ocurrencias y, como máximo, el doble (si cada ocurrencia se dividiera entre 2 bloques). ¡Esto no requeriría espacio adicional!
fuente
Si tenemos una cantidad mínima de
<unk>
(como lo espera la ley de Zipf),fuente
sed
lee una línea a la vez en la memoria independientemente. No podrá ajustarse a esta línea.sed
no hará el almacenamiento en búfer de entrada / salida cuando use este indicador. No puedo ver que leerá líneas parciales.