Je veux extraire tous les journaux entre deux horodatages. Certaines lignes peuvent ne pas avoir d'horodatage, mais je veux aussi ces lignes. En bref, je veux que chaque ligne tombe sous deux horodatages. Ma structure de journal ressemble à ceci:
[2014-04-07 23:59:58] CheckForCallAction [ERROR] Exception caught in +CheckForCallAction :: null
--Checking user--
Post
[2014-04-08 00:00:03] MobileAppRequestFilter [DEBUG] Action requested checkforcall
Supposons que je veuille extraire tout entre 2014-04-07 23:00
et 2014-04-08 02:00
.
Veuillez noter que l'horodatage de début ou l'horodatage de fin peuvent ne pas être présents dans le journal, mais je veux chaque ligne entre ces deux horodatages.
Vous pouvez utiliser awk
pour cela:
$ awk -F'[]]|[[]' \
'$0 ~ /^\[/ && $2 >= "2014-04-07 23:00" { p=1 }
$0 ~ /^\[/ && $2 >= "2014-04-08 02:00" { p=0 }
p { print $0 }' log
Où:
-F
Spécifie les caractères [
Et ]
Comme séparateurs de champ à l'aide d'une expression régulière$0
Fait référence à une ligne complète$2
Fait référence au champ de datep
est utilisé comme variable booléenne qui protège l'impression réelle$0 ~ /regex/
Est vrai si l'expression régulière correspond à $0
>=
Est utilisé pour comparer lexicographiquement une chaîne (équivalent à par exemple strcmp()
)La ligne de commande ci-dessus implémente intervalle de temps ouvert à droite correspondance. Pour obtenir une sémantique d'intervalle fermé, augmentez simplement votre bonne date, par exemple:
$ awk -F'[]]|[[]' \
'$0 ~ /^\[/ && $2 >= "2014-04-07 23:00" { p=1 }
$0 ~ /^\[/ && $2 >= "2014-04-08 02:00:01" { p=0 }
p { print $0 }' log
Si vous souhaitez faire correspondre des horodatages dans un autre format, vous devez modifier la sous-expression $0 ~ /^\[/
. Notez qu'il ignorait les lignes sans horodatage de la logique d'impression activée/désactivée.
Par exemple, pour un format d'horodatage comme YYYY-MM-DD HH24:MI:SS
(Sans accolades []
), Vous pouvez modifier la commande comme ceci:
$ awk \
'$0 ~ /^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-2][0-9]:[0-5][0-9]:[0-5][0-9]/
{
if ($1" "$2 >= "2014-04-07 23:00") p=1;
if ($1" "$2 >= "2014-04-08 02:00:01") p=0;
}
p { print $0 }' log
(notez que le séparateur de champs est également modifié - en transition vide/non vide, la valeur par défaut)
Consultez dategrep
sur https://github.com/mdom/dategrep
La description:
dategrep recherche dans les fichiers d'entrée nommés les lignes correspondant à une plage de dates et les imprime sur stdout.
Si dategrep fonctionne sur un fichier recherché, il peut effectuer une recherche binaire pour trouver la première et la dernière ligne à imprimer assez efficacement. dategrep peut également lire depuis stdin si l'un des arguments de nom de fichier n'est qu'un trait d'union, mais dans ce cas, il doit analyser chaque ligne qui sera plus lente.
Exemples d'utilisation:
dategrep --start "12:00" --end "12:15" --format "%b %d %H:%M:%S" syslog
dategrep --end "12:15" --format "%b %d %H:%M:%S" syslog
dategrep --last-minutes 5 --format "%b %d %H:%M:%S" syslog
dategrep --last-minutes 5 --format rsyslog syslog
cat syslog | dategrep --end "12:15" -
Bien que cette limitation puisse rendre cela inapproprié pour votre question exacte:
Pour le moment, dategrep mourra dès qu'il trouvera une ligne qui n'est pas analysable. Dans une future version, cela sera configurable.
Une alternative à awk
ou à un outil non standard consiste à utiliser GNU grep
pour ses greps contextuels. GNU's grep
vous permettra de spécifier le nombre de lignes après une correspondance positive à imprimer avec -A
et les lignes précédentes pour imprimer avec -B
Par exemple:
[davisja5@xxxxxxlp01 ~]$ cat test.txt
Ignore this line, please.
This one too while you're at it...
[2014-04-07 23:59:58] CheckForCallAction [ERROR] Exception caught in +CheckForCallAction :: null
--Checking user--
Post
[2014-04-08 00:00:03] MobileAppRequestFilter [DEBUG] Action requested checkforcall
we don't
want these lines.
[davisja5@xxxxxxlp01 ~]$ egrep "^\[2014-04-07 23:59:58\]" test.txt -A 10000 | egrep "^\[2014-04-08 00:00:03\]" -B 10000
[2014-04-07 23:59:58] CheckForCallAction [ERROR] Exception caught in +CheckForCallAction :: null
--Checking user--
Post
[2014-04-08 00:00:03] MobileAppRequestFilter [DEBUG] Action requested checkforcall
Ce qui précède dit essentiellement à grep
d'imprimer les 10 000 lignes qui suivent la ligne qui correspond au modèle sur lequel vous souhaitez commencer, ce qui fait que votre sortie commence là où vous le souhaitez et va jusqu'à la fin (espérons ) alors que le second egrep
dans le pipeline lui indique d'imprimer uniquement la ligne avec le délimiteur de fin et les 10 000 lignes qui le précèdent. Le résultat final de ces deux est de commencer où vous voulez et de ne pas aller là où vous lui avez dit d'arrêter.
10000 n'est qu'un chiffre que j'ai trouvé, n'hésitez pas à le changer en un million si vous pensez que votre sortie va être trop longue.
Utilisation de sed:
#!/bin/bash
E_BADARGS=23
if [ $# -ne "3" ]
then
echo "Usage: `basename $0` \"<start_date>\" \"<end_date>\" file"
echo "NOTE:Make sure to put dates in between double quotes"
exit $E_BADARGS
fi
isDatePresent(){
#check if given date exists in file.
local date=$1
local file=$2
grep -q "$date" "$file"
return $?
}
convertToEpoch(){
#converts to Epoch time
local _date=$1
local Epoch_date=`date --date="$_date" +%s`
echo $Epoch_date
}
convertFromEpoch(){
#converts to date/time format from Epoch
local Epoch_date=$1
local _date=`date --date="@$Epoch_date" +"%F %T"`
echo $_date
}
getDates(){
# collects all dates at beginning of lines in a file, converts them to Epoch and returns a sequence of numbers
local file="$1"
local state="$2"
local i=0
local date_array=( )
if [[ "$state" -eq "S" ]];then
datelist=`cat "$file" | sed -r -e "s/^\[([^\[]+)\].*/\1/" | egrep "^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}"`
Elif [[ "$state" -eq "E" ]];then
datelist=`tac "$file" | sed -r -e "s/^\[([^\[]+)\].*/\1/" | egrep "^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}"`
else
echo "Something went wrong while getting dates..." 1>&2
exit 500
fi
while read _date
do
Epoch_date=`convertToEpoch "$_date"`
date_array[$i]=$Epoch_date
#echo "$_date" "$Epoch_date" 1>&2
(( i++ ))
done<<<"$datelist"
echo ${date_array[@]}
}
findneighbours(){
# search next best date if date is not in the file using recursivity
IFS="$old_IFS"
local elt=$1
shift
local state="$1"
shift
local -a array=( "$@" )
index_pivot=`expr ${#array[@]} / 2`
echo "#array="${#array[@]} ";array="${array[@]} ";index_pivot="$index_pivot 1>&2
if [ "$index_pivot" -eq 1 -a ${#array[@]} -eq 2 ];then
if [ "$state" == "E" ];then
echo ${array[0]}
Elif [ "$state" == "S" ];then
echo ${array[(( ${#array[@]} - 1 ))]}
else
echo "State" $state "undefined" 1>&2
exit 100
fi
else
echo "elt with index_pivot="$index_pivot":"${array[$index_pivot]} 1>&2
if [ $elt -lt ${array[$index_pivot]} ];then
echo "elt is smaller than pivot" 1>&2
array=( ${array[@]:0:(($index_pivot + 1)) } )
else
echo "elt is bigger than pivot" 1>&2
array=( ${array[@]:$index_pivot:(( ${#array[@]} - 1 ))} )
fi
findneighbours "$elt" "$state" "${array[@]}"
fi
}
findFirstDate(){
local file="$1"
echo "Looking for first date in file" 1>&2
while read line
do
echo "$line" | egrep -q "^\[[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\]" &>/dev/null
if [ "$?" -eq "0" ]
then
#echo "line=" "$line" 1>&2
firstdate=`echo "$line" | sed -r -e "s/^\[([^\[]+)\].*/\1/"`
echo "$firstdate"
break
else
echo $? 1>&2
fi
done< <( cat "$file" )
}
findLastDate(){
local file="$1"
echo "Looking for last date in file" 1>&2
while read line
do
echo "$line" | egrep -q "^\[[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\]" &>/dev/null
if [ "$?" -eq "0" ]
then
#echo "line=" "$line" 1>&2
lastdate=`echo "$line" | sed -r -e "s/^\[([^\[]+)\].*/\1/"`
echo "$lastdate"
break
else
echo $? 1>&2
fi
done< <( tac "$file" )
}
findBestDate(){
IFS="$old_IFS"
local initdate="$1"
local file="$2"
local state="$3"
local first_elts="$4"
local last_elts="$5"
local date_array=( )
local initdate_Epoch=`convertToEpoch "$initdate"`
if [[ $initdate_Epoch -lt $first_elt ]];then
echo `convertFromEpoch "$first_elt"`
Elif [[ $initdate_Epoch -gt $last_elt ]];then
echo `convertFromEpoch "$last_elt"`
else
date_array=( `getDates "$file" "$state"` )
echo "date_array="${date_array[@]} 1>&2
#first_elt=${date_array[0]}
#last_elt=${date_array[(( ${#date_array[@]} - 1 ))]}
echo `convertFromEpoch $(findneighbours "$initdate_Epoch" "$state" "${date_array[@]}")`
fi
}
main(){
init_date_start="$1"
init_date_end="$2"
filename="$3"
echo "problem start.." 1>&2
date_array=( "$init_date_start","$init_date_end" )
flag_array=( 0 0 )
i=0
#echo "$IFS" | cat -vte
old_IFS="$IFS"
#changing separator to avoid whitespace issue in date/time format
IFS=,
for _date in ${date_array[@]}
do
#IFS="$old_IFS"
#echo "$IFS" | cat -vte
if isDatePresent "$_date" "$filename";then
if [ "$i" -eq 0 ];then
echo "Starting date exists" 1>&2
#echo "date_start=""$_date" 1>&2
date_start="$_date"
else
echo "Ending date exists" 1>&2
#echo "date_end=""$_date" 1>&2
date_end="$_date"
fi
else
if [ "$i" -eq 0 ];then
echo "start date $_date not found" 1>&2
else
echo "end date $_date not found" 1>&2
fi
flag_array[$i]=1
fi
#IFS=,
(( i++ ))
done
IFS="$old_IFS"
if [ ${flag_array[0]} -eq 1 -o ${flag_array[1]} -eq 1 ];then
first_elt=`convertToEpoch "$(findFirstDate "$filename")"`
last_elt=`convertToEpoch "$(findLastDate "$filename")"`
border_dates_array=( "$first_elt","$last_elt" )
#echo "first_elt=" $first_elt "last_elt=" $last_elt 1>&2
i=0
IFS=,
for _date in ${date_array[@]}
do
if [ $i -eq 0 -a ${flag_array[$i]} -eq 1 ];then
date_start=`findBestDate "$_date" "$filename" "S" "${border_dates_array[@]}"`
Elif [ $i -eq 1 -a ${flag_array[$i]} -eq 1 ];then
date_end=`findBestDate "$_date" "$filename" "E" "${border_dates_array[@]}"`
fi
(( i++ ))
done
fi
sed -r -n "/^\[${date_start}\]/,/^\[${date_end}\]/p" "$filename"
}
main "$1" "$2" "$3"
Copiez ceci dans un fichier. Si vous ne voulez pas voir les informations de débogage, le débogage est envoyé à stderr alors ajoutez simplement "2>/dev/null"