![Procesar registros/párrafos grandes](https://rvso.com/image/178469/Procesar%20registros%2Fp%C3%A1rrafos%20grandes.png)
Tengo un archivo de texto grande (300 MB) con registros \n\n
como separador. Cada línea es un campo y comienza con un número (la etiqueta/nombre del campo) seguido de un carácter TAB y el contenido/valor del campo:
110 something from record 1, field 110
149 something else
111 any field could be repeatable
111 any number of times
120 another field
107 something from record 2, field 107
149 fields could be repeatable
149 a lot of times
149 I mean a LOT!
130 another field
107 something from record 3
149 something else
Cada registro no debe tener más de 100 KB.
Pude encontrar algunos registros problemáticos (mayores que el límite) aleliminar finales de línea de estos registros/"párrafos"yobteniendo su longitud:
cat records.txt | awk ' /^$/ { print; } /./ { printf("%s ", $0); } ' | awk '{print length+1}' | sort -rn | grep -P "^\d{6,}$"
Estoy tratando de encontrar una manera de procesar esos registros no válidos:
- eliminando registros mayores que el límite.
- eliminar todas las apariciones de una etiqueta problemática conocida en particular (149 en el ejemplo anterior). Es aceptable la hipótesis de que ningún registro superará el límite si se eliminan todas las líneas que comienzan con el campo 149.
Probablemente, eliminar suficientes apariciones de un campo/etiqueta en particular para que quepa por debajo del límite merece un script completo. Sería incluso mejor eliminar primero los últimos.
Esto está relacionado con un antiguo formato de archivo de bibliotecario llamadoISO 2709.
Respuesta1
Si solo desea omitir los registros problemáticos:
awk 'BEGIN { ORS=RS="\n\n" } length <= 100*1000' file
Esto imprime cada registro que tenga menos o igual a 100k caracteres.
Para eliminar los campos que comienzan con un número entero positivo particular, si el registro es demasiado grande:
awk -v number=149 'BEGIN { ORS=RS="\n\n"; OFS=FS="\n" }
length <= 100*1000 { print; next }
{
# This is a too long record.
# Re-create it without any fields whose first tab-delimited
# sub-field is the number in the variable number.
# Split the record into an array of fields, a.
nf = split($0,a)
# Empty the record.
$0 = ""
# Go through the fields and add back the ones that we
# want to the output record.
for (i = 1; i <= nf; ++i) {
split(a[i],b,"\t")
if (b[1] != number) $(NF+1) = a[i]
}
# Print the output record.
print
}' file
Esto imprime registros cortos, como antes. Los registros más largos se eliminan de todos los campos cuyo primer subcampo delimitado por tabulaciones sea el número number
(indicado aquí en la línea de comando como 149).
Para registros grandes, el registro se vuelve a crear sin los campos que no queremos. El bucle interno recrea el registro de salida dividiendo los campos en pestañas y agregando aquellos cuyo primer subcampo delimitado por tabulaciones no es number
:
for (i = 1; i <= nf; ++i) {
split(a[i],b,"\t")
if (b[1] != number) $(NF+1) = a[i]
}
Dado que la especificación POSIX awk
deja lo que sucede cuando tiene un valor de varios caracteres RS
sin especificar (la mayoría de las implementaciones lo tratan como una expresión regular), puede usar RS=""; ORS="\n\n"
en lugar de ORS=RS="\n\n"
cuando use su awk
implementación estrictamente conforme. Si hace esto, tenga en cuenta que varias líneas en blanco en los datos ya no delimitarán registros vacíos.
Respuesta2
Otro awk
enfoque:
awk -v lim=99999 'BEGIN{RS=""; ORS="\n\n"}\
{while (length()>=lim) {if (!sub(/\n149\t[^\n]*/,"")) break;}} length()<lim' file
Esto eliminará gradualmente las líneas que comienzan con 149
si la longitud del registro está por encima del límite especificado en la variable lim
, sustituyéndolas por "nada", hasta que se haya mantenido el límite o no sea posible realizar más reducciones (indicado por el número de sustituciones reales). siendo 0). Entonces solo imprimirá registros cuya longitud final sea menor que el límite.
Desventaja:Eliminará las 149
líneas que comienzan desde la primera, por lo que si constituyen elementos individuales de un texto contiguo, ese texto se volverá algo incomprensible.
Nota:Especificar RS=""
en lugar de lo explícito RS="\n\n"
es loportátilforma de uso awk
en "modo párrafo", ya que el comportamiento de varios caracteres RS
no está definido por la especificación POSIX. Sin embargo, si puede habervacíoregistros en su archivo, serán ignorados awk
y, en consecuencia, no aparecerán en la salida. Si esto no es lo que desea, es posible que deba utilizar la RS="\n\n"
notación explícita: la mayoría de awk
las implementaciones la tratarán como una expresión regular y harán lo que uno esperaría "ingenuamente".
Respuesta3
Siempre que tenga \n\n
como separador de registros, piense en Perl y en modo párrafo (de man perlrun
):
-0[octal/hexadecimal]
specifies the input record separator ($/) as an octal or hexadecimal number.
[...]
The special value 00 will cause Perl to slurp files in paragraph mode.
Usando eso, puedes hacer:
Eliminar todos los registros de más de 100.000caracteres(tenga en cuenta que es posible que esto no sea lo mismo que bytes, según la codificación de su archivo):
perl -00 -ne 'print unless length()>100000' file
Recorte todos los registros que tengan más de 100 000 caracteres eliminando todos los caracteres después de los primeros 100 000:
perl -00 -lne 'print substr($_,0,100000)' file
Eliminar líneas que comienzan con
149
:perl -00 -pe 's/(^|\n)149\s+[^\n]+//g;' file
Elimine las líneas que comiencen con
149
pero solo si este registro tiene más de 100000 caracteres:perl -00 -pe 's/(^|\n)149\s+[^\n]+//g if length()>100000; ' file
Si un registro tiene más de 100.000 caracteres, elimine las líneas que comienzan con
149
hasta que el registro tenga menos de 100.000 caracteres o no haya más líneas con 149:perl -00 -pe 'while(length()>100000 && /(^|\n)149\s/){s/(^|\n)149\s+[^\n]+//}' file
Si un registro tiene más de 100.000 caracteres, elimine las líneas que comienzan con
149
hasta que el registro tenga menos de 100.000 caracteres o no haya más líneas con 149, y si esaúntiene más de 100.000 caracteres, imprima sólo los primeros 100.000:perl -00 -lne 'while(length()>100000 && /(^|\n)149\s/){ s/(^|\n)149\s+[^\n]+// } print substr($_,0,100000)' file
Finalmente, como arriba, pero elimine líneas enteras, no solo caracteres, hasta obtener el tamaño correcto para no tener registros truncados:
perl -00 -ne 'while(length()>100000 && /(^|\n)149\s/){ s/(^|\n)149\s+[^\n]+// } map{ $out.="$_\n" if length($out . "\n$_")<=100000 }split(/\n/); print "$out\n"; $out="";' file
Respuesta4
Probablemente podría ser más elegante, pero aquí tienes una solución:
cat records.txt | awk -v RS='' '{if (length>99999) {gsub(/\n149\t[^\n]*\n/,"\n");print $0"\n"} else {print $0"\n"} }'
Soy consciente del uso inútil de cat, creo.Es más claro el flujo de izquierda a derecha..
Donde 99999 es el tamaño del umbral y 149 el inicio de la línea (nombre del campo) que se eliminará en ese caso.
Utilizo un no codicioso \n149\t[^\n]*\n/
para eliminar solo lo que sería ^149\t.*$
.
gsub
reemplaza el patrón con la cadena especificada y devuelve el número de sustituciones/reemplazos realizados.
Fue inspirado enesta respuesta.