bash - script - sed reemplazar/
¿Cómo puedo usar bash(grep/sed/etc) para tomar una sección de un archivo de registro entre 2 marcas de tiempo? (5)
Aquí una idea básica de cómo hacerlo:
- Examine la marca de fecha en el archivo para ver si es irrelevante
- Si puede ser relevante, descomprima si es necesario y examine la primera y la última línea del archivo para ver si contiene la hora de inicio o finalización.
- Si lo hace, use una función recursiva para determinar si contiene la hora de inicio en la primera o la segunda mitad del archivo. Utilizando una función recursiva, creo que podría encontrar cualquier fecha en un millón de archivos de registro con alrededor de 20 comparaciones.
- repetir el (los) archivo (s) de registro en orden desde el desplazamiento de la primera entrada hasta el desplazamiento de la última entrada (no más comparaciones)
Lo que no sé es cómo leer mejor la enésima línea de un archivo (¿qué tan eficiente es usar la cola n + ** n | head 1 **?)
¿Alguna ayuda?
Tengo un conjunto de registros de correo: mail.log mail.log.0 mail.log.1.gz mail.log.2.gz
cada uno de estos archivos contiene líneas cronológicamente ordenadas que comienzan con marcas de tiempo como:
3 de mayo 13:21:12 ...
¿Cómo puedo capturar fácilmente cada entrada de registro después de una fecha / hora determinada y antes de otra fecha / hora usando bash (y las herramientas de línea de comando relacionadas) sin comparar cada línea? Tenga en cuenta que mis fechas de antes y después pueden no coincidir exactamente con ninguna entrada en los archivos de registro.
Me parece que necesito determinar el desplazamiento de la primera línea mayor que la marca de tiempo inicial, y el desplazamiento de la última línea menor que la marca de tiempo final, y cortar esa sección de alguna manera.
Convierte tus fechas min / max en "segundos desde época",
MIN=`date --date="$1" +%s`
MAX=`date --date="$2" +%s`
Convierta las primeras n
palabras en cada línea de registro al mismo,
L_DATE=`echo $LINE | awk ''{print $1 $2 ... $n}''`
L_DATE=`date --date="$L_DATE" +%s`
Compara y tira las líneas hasta llegar a MIN
,
if (( $MIN > $L_DATE )) ; then continue ; fi
Compara e imprime líneas hasta llegar a MAX
if (( $L_DATE <= $MAX )) ; then echo $LINE ; fi
Salga cuando exceda MAX
.
if (( $L_DATE > $MAX )) ; then exit 0 ; fi
Toda la secuencia de comandos minmaxlog.sh se ve así,
#!/usr/bin/env bash
MIN=`date --date="$1" +%s`
MAX=`date --date="$2" +%s`
while true ; do
read LINE
if [ "$LINE" = "" ] ; then break ; fi
L_DATE=`echo $LINE | awk ''{print $1 " " $2 " " $3 " " $4}''`
L_DATE=`date --date="$L_DATE" +%s`
if (( $MIN > $L_DATE )) ; then continue ; fi
if (( $L_DATE <= $MAX )) ; then echo $LINE ; fi
if (( $L_DATE > $MAX )) ; then break ; fi
done
Lo ejecuté en este archivo minmaxlog.input ,
May 5 12:23:45 2009 first line
May 6 12:23:45 2009 second line
May 7 12:23:45 2009 third line
May 9 12:23:45 2009 fourth line
June 1 12:23:45 2009 fifth line
June 3 12:23:45 2009 sixth line
Me gusta esto,
./minmaxlog.sh "May 6" "May 8" < minmaxlog.input
Puede ser posible en un entorno Bash, pero realmente debería aprovechar las herramientas que tienen más soporte incorporado para trabajar con cadenas y fechas. Por ejemplo, Ruby parece tener la capacidad incorporada para analizar su formato de fecha. A continuación, puede convertirlo en una marca de tiempo de Unix fácilmente comparable (un entero positivo que representa los segundos desde la época).
irb> require ''time''
# => true
irb> Time.parse("May 3 13:21:12").to_i
# => 1241371272
A continuación, puede escribir fácilmente un script de Ruby:
- Proporcione una fecha de inicio y finalización. Convierta esos a este número de marca de tiempo de Unix.
- Escanee los archivos de registro línea por línea, convirtiendo la Fecha en su Marca de tiempo Unix y verifique si está dentro del rango de las fechas de inicio y finalización.
Nota: La conversión a un número entero de marca de tiempo Unix es buena porque comparar números enteros es muy fácil y eficiente de hacer.
Usted mencionó "sin comparar cada línea". Va a ser difícil "adivinar" dónde en el archivo de registro las entradas comienzan siendo demasiado antiguas o demasiado nuevas sin verificar todos los valores intermedios. Sin embargo, si de hecho hay una tendencia creciente monótona, entonces sabrá inmediatamente cuándo detener las líneas de análisis sintáctico, ya que tan pronto como la siguiente entrada sea demasiado nueva (o antigua, dependiendo del diseño de los datos) sabrá que puede dejar de buscar. Aún así, existe el problema de encontrar la primera línea en su rango deseado.
Acabo de notar tu edición. Esto es lo que diría:
Si realmente te preocupa encontrar de manera eficiente esa entrada inicial y final, entonces podrías hacer una búsqueda binaria para cada una. O, si eso parece excesivo o demasiado difícil con las herramientas bash, podría tener una heurística de leer solo el 5% de las líneas (1 de cada 20), acercarse rápidamente a la respuesta exacta y luego refinarla si lo desea. Estas son solo algunas sugerencias para mejorar el rendimiento.
Tal vez puedas probar esto:
sed -n "/BEGIN_DATE/,/END_DATE/p" logfile
Tienes que mirar cada línea del rango que quieras (para saber si está en el rango que deseas), así que supongo que te refieres no a todas las líneas del archivo. Como mínimo, tendrá que mirar cada línea del archivo hasta la primera que esté fuera de su rango (supongo que las líneas están en orden de fecha / hora).
Este es un patrón bastante simple:
state = preprint
for every line in file:
if line.date >= startdate:
state = print
if line.date > enddate:
exit for loop
if state == print:
print line
Puedes escribir esto en awk, Perl, Python, incluso COBOL si es necesario, pero la lógica es siempre la misma.
Localizar los números de línea primero (con decir grep) y luego simplemente imprimir ciegamente ese rango de línea no ayudará ya que grep también tiene que mirar todas las líneas ( todas ellas, no solo hasta la primera fuera del rango, y la mayoría probablemente dos veces , una para la primera línea y otra para la última).
Si esto es algo que va a hacer con bastante frecuencia, puede considerar cambiar el esfuerzo de ''cada vez que lo hace'' a ''una vez, cuando el archivo esté estabilizado''. Un ejemplo sería cargar las líneas del archivo de registro en una base de datos, indexada por la fecha / hora.
Eso demorará un tiempo en configurarse, pero hará que sus consultas sean mucho más rápidas. No necesariamente estoy abogando por una base de datos; probablemente podría lograr el mismo efecto dividiendo los archivos de registro en registros por hora de esta manera:
2009/
01/
01/
0000.log
0100.log
: :
2300.log
02/
: :
Luego, durante un tiempo dado, sabes exactamente por dónde empezar y dejar de buscar. La gama 2009/01/01-15:22
hasta 2009/01/05-09:07
resultaría en:
- algunos (el último bit) del archivo
2009/01/01/1500.txt
. - todos los archivos
2009/01/01/1[6-9]*.txt
. - todos los archivos
2009/01/01/2*.txt
. - todos los archivos
2009/01/0[2-4]/*.txt
. - todos los archivos
2009/01/05/0[0-8]*.txt
. - algunos (el primer bit) del archivo
2009/01/05/0900.txt
.
Por supuesto, escribiría un script para devolver esas líneas en lugar de intentar hacerlo manualmente cada vez.