sustituir script reemplazar manejo linea insertar especiales espacios eliminar comando caracteres cadenas blanco bash parsing logfiles timestamp

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:

  1. Examine la marca de fecha en el archivo para ver si es irrelevante
  2. 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.
  3. 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.
  4. 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.