Linuxportalen.se är Sveriges största och aktivaste webbplats för användare av öppen- och fri programvara.
Du besöker Linuxportalen.se som gäst vilket begränsar din möjlighet att använda webbplatsens alla funktioner. Genom att registera dig som medlem får du inte bara möjlighet att söka bland webbplatsens innehåll, skapa nya och delta i befintliga diskussioner, skapa din egen blogg, kommunicera med andra medlemmar genom privata meddelanden och delta i omröstningar. Du får också tillgång till Veckans Kadavro - en seriestrip unikt skapad för Linuxportalen.se!
Registeringen sker snabbt och är helt kostnadsfri - tveka inte, bli medlem idag!
Hej,
Jag har en enormt stor logfil med tidsstämplar som jag skulle vilja göra en sökning i. Den kan se ut t.ex. så här:
#######################################################################
[<(YYYY-MM-DD)-1>] <text>
<text>
<text>
<text>
|
|
[<YYYY-MM-DD>] <text>
<text>
<text>
<text>
|
|
#######################################################################
All text har alltså inte alltid en tidsstämpel framför sig.
Jag skulle vilja göra en sökning i filen med hjälp av bash-script, awk, sed eller nåt annat lämpligt verktyg, men är inte intresserad av vad som hände "igår". Vill endast söka från första förekomsten av dagens datum och neråt i filen. Hur får man till det?
Vi kan använda sed för detta. Kommandot 'd' tar bort rader och med en adressrymd kan vi ange vilka rader vi vill "ta bort". Vi anger startadress som /YYYY-MM-DD/, dvs. ett reguljärt uttryck för att hitta YYYY-MM-DD, och $ som slutadress då det symboliserar slutet på filen. Med hjälp av '!' vänder vi på begreppet och tar bort alla rader som inte ingår i adressrymden. Därfter kan vi skicka detta genom grep.
Då får vi följande för att skriva ut alla träffar av <text> från första tidstämpeln YYYY-MM-DD till slutet av filen.
sed '/YYYY-MM-DD/,$!d' loggfil | grep <text>
Kan ändra till /^YYYY-MM-DD/ om du vill vara säker på att första datum-träffen är i början på en rad i loggfilen.
/Micke
Går säkert att göra snyggare men det kan kanske vara till hjälp.
Tack för tipsen. De fungerade precis så som jag vill ha det. Nu har det dock kommit önskemål som gör den här sökningen lite mer avancerad.
Det finns ett schemalagt jobb som gör denna sökning 3 ggr om dagen. Ponera att man hittar det man söker vid första tillfället. När sökningen sen görs igen så hittas samma exakt sökargument en gång till eftersom sökningen påbörjas från samma ställe. Finns det nåt sätt att se till att sökningen utesluter den första träffen, om resultatet av föregående sökning var lyckosamt, och fortsätter därifrån i filen? Och om resultatet av första sökningen INTE var lyckosamt så kan sökningen alltid påbörjas från samma punkt i filen.
Tanken är att sökningen ska göras en gång i halvtimmen. Blir många mail då......
Hoppas min beskrivning är tydlig
Är det verkligen bara den första träffen du vill utesluta i nästa sökning? Verkar mer rimligt med alla träffar. Annars kommer du hitta exakt samma sökargument igen förutom den första... Dessutom så borde en ny sökning alltid påbörjas där den senaste slutade oavsett om man hittade något eller ej. Det är ju samma sökargument, så hittade du inget i den förra sökningen så kommer du inte hitta något denna gången heller fram till och med den sista raden som söktes igenom i föregående sökning...
Kanske jag har missförstått något i din beskrivning?
Om loggfilen enbart växer, dvs. ingen "loggfil-rotation", så påbörjar man i så fall varje ny sökning där den senaste slutade. Med andra ord, raden efter den sista raden i föregående sökning blir den första i nästa sökning. Om loggfilen "roteras" så måste man nollställa startraden samtidigt.
Snabbt exempel på ett skript som man kan köra som ett schemalagt jobb.
#!/bin/sh
# Search pattern can be anything that is accepted by grep.
PATTERN='<text>'
LOGFILE='<logfile>'
AWKSCRIPT='awkscr'
TODAY=$(date +"%Y-%m-%d")
# Create awk script searching from top of file.
if [ ! -f $AWKSCRIPT ]; then
echo "/^$TODAY/,0" > $AWKSCRIPT
fi
# Perform search.
awk -f $AWKSCRIPT $LOGFILE | grep "$PATTERN"
# Create new awk script that only cares about lines after the last line in the previous search.
echo "/^$TODAY/,0{ if (NR > $(awk 'END{print NR}' $LOGFILE)) print}" > $AWKSCRIPT
Skriptet kontrollerar om det redan finns ett awk-skript (AWKSCRIPT bör peka på en fil som inte vem som helst kan skriva till), om så inte är fallet, dvs. första gången man kör skriptet för denna loggfil, så skapas awk-skriptet. /^$TODAY/,0 innebär att man startar vid första träffen av dagens datum och fortsätter till slutet av filen (0 är alltid falskt så man kommer aldrig "hitta" detta sökargument). Därefter kör man detta skript på loggfilen och vidare genom grep för själva sökargumentet. Slutligen så sparar man ett nytt awk-skript med följande format /^$TODAY/,0{ if (NR > <sista raden i loggfilen>) print }, dvs. förutom den vanliga tidstämpel-till-slut-av-filen kontrollen, så kontrollerar man vid nästa körning även att träffen är efter den sista raden i föregående sökning.
Varje gång som loggfilen "roteras" så ser man till att samtidigt ta bort awkscr-filen (t.ex. via cron), så att den kan återskapas vid nästa körning och då söka från början av filen.
Hoppas ovanstående var begripligt
/Micke
Det finns ett problem med ovanstående lösning. Om man kör skriptet dag 1 och sedan nästa körning dag 2 så kommer det genererade awk-skriptet innehålla fel datum. Man skulle kunna lösa det genom att inkludera rätt $TODAY före varje körning av awk-skriptet. T.ex.
#!/bin/sh
# Search pattern can be anything that is accepted by grep.
PATTERN='<text>'
LOGFILE='<logfile>'
AWKSCRIPT='awkscr'
TODAY=$(date +"%Y-%m-%d")
# Create awk script searching from top of file.
if [ ! -f $AWKSCRIPT ]; then
echo "/TODAY/,0" > $AWKSCRIPT
fi
# Perform search.
sed -i "s/TODAY/^$TODAY/" $AWKSCRIPT
awk -f $AWKSCRIPT $LOGFILE | grep "$PATTERN"
# Create new awk script that only cares about lines after the last line in the previous search.
echo "/TODAY/,0{ if (NR > $(awk 'END{print NR}' $LOGFILE)) print}" > $AWKSCRIPT
Skillnaden mot tidigare skript är att awk-skriptet nu innehåller texten TODAY och denna sträng ersätts med dagens datum före varje sökning.
/Micke
När jag tänker efter så borde inte datumen ha så stor betydelse förutom i den allra första sökningen. Därefter borde det räcka med att starta från slutet av förra sökningen (speciellt om man ska utföra denna sökning varje halvtimme). Då kan man använda följande skript (som använder sed istället för awk)
#!/bin/sh
# Search pattern can be anything that is accepted by grep.
PATTERN='<text>'
LOGFILE='<logfile>'
SEDSCRIPT='sedscr'
TODAY=$(date +"%Y-%m-%d")
# Create sed script searching from top of file.
if [ ! -f $SEDSCRIPT ]; then
echo "/^$TODAY/,\$!d" > $SEDSCRIPT
fi
# Perform search.
sed -f $SEDSCRIPT $LOGFILE | grep "$PATTERN"
# Create new sed script that only cares about lines after the last line in the previous search.
echo "$(awk 'END{print NR+1}' $LOGFILE),\$!d" > $SEDSCRIPT
/Micke
Jag är grön på området så exakt vad allt gör i det du beskriver går nog inte in riktigt
. Men copy/paste är väl den mest användna tangentkombinationen i IT-branschen
?
Men en detalj jag missat är att loggfilen, förutom datumstämpel, också innehåller tidsstämpel. Formatet är så här:
[2009-06-24 16:36:08 <text>] <text>
Variabeln TODAY skulle alltså kunna sättas
TODAY=$(date +"%F %T")
Förändrar det nånting, tro? En mer exakt sökning på tidpunkt kan ju då göras?
Dessutom är fallet sådant att vi inte bara har EN logfil. Det är flera olika loggfiler i samma mapp. Vilket gör att hanteringen av radnummer är unik per fil. Förändrar kanske också en del i resonemanget?
En bra början när man ska lösa ett problem är att först definiera själva problemet
För det ursprungliga problemet, dvs. "söka från första förekomsten av dagens datum och neråt i filen", så hade det betydelse att söka efter en specifik datum (inklusive tidpunkt), men med den senare beskrivningen (sök varje halvtimme och ignorera tidigare träffar), så ser jag inte något behov av att begränsa adressrymden baserat på datum/tidpunkt. Hela TODAY-delen kan egentligen tas bort. I ovanstående sed-skript så behöll jag den hanteringen för första sökningen, men man skulle lika gärna kunna ta bort den och innan första sökningen skapa en tom SEDSCRIPT-fil. Därefter återskapas den automatiskt med korrekt radnummer att påbörja sökningen från.
För att hantera flera loggfiler så kan du ju alltid skapa mer än ett skript... Givetvis är hanteringen av radnummer unik per fil, dvs. SEDSCRIPT behöver peka på en unik fil (ett enkelt namngivningsystem skulle t.ex. kunna vara SEDSCRIPT=<logfile>.sed).
Nu vet jag inte om jag förstod. Loggfilerna har ju trots allt datum och tidsstämplar långt tillbaka under vintern och våren. Jag vill fortfarande inte att sökningen ska hitta nåt före just dagens datum om TODAY stryks?
Och sorry min scriptokunskap, men stycket
if [ ! -f $SEDSCRIPT ]; then
echo "/^$TODAY/,\$!d" > $SEDSCRIPT
fi
funkar inte i vare sig bash eller sh. Kan skilt-från-operatorn ha en annan betydelse för echo?
if [ ! -f $SEDSCRIPT ]; then
> echo "/^$TODAY/,$!d" > $SEDSCRIPT
-bash: !d": event not found
> echo "/^$TODAY/,$\!d" > $SEDSCRIPT
> fi
Om du skapar en tom SEDSCRIPT-fil och kör skriptet en gång så kommer SEDSCRIPT-filen nu innehålla korrekt information, dvs. NN,$!d där NN är sista radnumret i loggfilen just nu. Alla sökningar därefter kommer ignorera tidigare text i loggfilen.
När det gäller din andra fråga så börjar vi röra oss utanför ämnet. Jag visade dig ett exempel på ett skript och du försöker istället köra innehållet manuellt från kommandoraden i bash. Om du kontrollerar vad ! används till från kommandoraden i bash så kommer du upptäcka att den används till att komma åt historiken. !d" innebär att den försöker hitta ett tidigare kommando som startar med d" och det kommer den troligtvis aldrig hitta