[bash] Laatste stuk uit een file greppen

Pagina: 1
Acties:
  • 106 views sinds 30-01-2008
  • Reageer

  • pierre-oord
  • Registratie: April 2002
  • Laatst online: 15-01 10:55
Ik wil uit een lange tekstfile steeds een stuk tekst halen wat later is toegevoegd.

Ik echo eerst een bepaalde lijn in het bestand ter herkenning. Nu is dat "Datum: $datum".

Daarna doe ik:
cat file |grep -A 1000 Datum: | grep -v Datum:

Zo krijg ik alles wat onder datum staat (en niet het regeltje datum zelf, daar zorgt de laatste grep voor).

Maar nu: Ik doe dit meerdere malen in een bestand. Nu krijg ik alleen van de laatste match de laatste 1000 regels te zien.

Het valt nog te doen, ik krijg dan zo'n output na 2x een datum echoen:
Tue Jan 11 18:52:39 CET 2005
Tue Jan 11 18:53:13 CET 2005
kljfaljda


En van die datum's krijg ik er dus steeds maar meer.

Hoe kan ik nu op een nette manier gewoon het laatste stuk van een file lezen? Eigenlijk moet "onderaan" worden begonnen met inlezen, totdat "Datum:" ergens staat.

Alvast bedankt!

Ondernemer in tech (oud LOQED.com, nu UpToMore.com)


Verwijderd

Kan je hier niet beter SED voor gebruiken?

http://www.cornerstonemag.com/sed/

Zit standaard in iedere linux distro

  • VyperX
  • Registratie: Juni 2001
  • Laatst online: 09-02 09:12
En anders misschien "tail" ?

edit: Moet beter leren lezen... nvm

[ Voor 79% gewijzigd door VyperX op 11-01-2005 19:05 ]

My Dwarf Fortress ASCII Reward: ~~@~~####,.".D",.B""


  • woutur
  • Registratie: Maart 2000
  • Laatst online: 13-02 21:34

woutur

Klauwtjes uit!

Is er niet iets te verzinnen in combinatie met tail ?

/edit:
Iemand was me net voor :)

[ Voor 26% gewijzigd door woutur op 11-01-2005 19:03 ]

Als je niet de moeite neemt je post in net Nederlands te schrijven, neem ik de moeite niet hem te lezen.


  • Kippenijzer
  • Registratie: Juni 2001
  • Laatst online: 11-02 20:53

Kippenijzer

McFallafel, nu met paardevlees

idd, ik *meen* dat je met grep het regelnummer kunt achterhalen, tel de regels, vraag met grep op welke regelnummer datum staat, en tail -n <totale regels - datum-regelnummer>. Denk ik althans :P

  • blaataaps
  • Registratie: Juli 2001
  • Niet online
code:
1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/perl -w
open FILE,"<jouwbestand";
$print = "false";
while (<FILE>) {
  if( $print eq "true"){
    print $_;
  }
  if( $_ eq "vanafhier\n"){
    $print = "true";
  }
}
het is vast een ontzettend ranzig stuk perl, heb het snel even inelkaar gezet, maar het werkt hier :)
Dit valt wel te verfraaien en aan te passen, de lezer moet ook nog wat te doen hebben natuurlijk :)

[ Voor 23% gewijzigd door blaataaps op 11-01-2005 20:05 ]


Verwijderd

Zou je niet iets met tac kunnen? Werkt net als cat, maar dan precies andersom :)

  • odysseus
  • Registratie: Augustus 2000
  • Laatst online: 08:56

odysseus

Debian GNU/Linux Sid

blaataaps schreef op dinsdag 11 januari 2005 @ 20:03:
code:
1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/perl -w
open FILE,"<jouwbestand";
$print = "false";
while (<FILE>) {
  if( $print eq "true"){
    print $_;
  }
  if( $_ eq "vanafhier\n"){
    $print = "true";
  }
}
het is vast een ontzettend ranzig stuk perl, heb het snel even inelkaar gezet, maar het werkt hier :)
Dit valt wel te verfraaien en aan te passen, de lezer moet ook nog wat te doen hebben natuurlijk :)
Volgens mij doet jouw code niet wat de topicstarter wil: bij jou wordt immers alles geprint na de eerste keer dat 'vanafhier' gevonden wordt, en de TS wil nu juist alles hebben vanaf de _laatste_ keer dat de tekst voorkomt. Dat betekent dus feitelijk dat je twee keer door het bestand heen moet of dat je het op een handigere manier aan moet pakken. Zelf zou ik een buffer maken dat alles bevat van de laatste keer dat je de tekst ben tegengekomen tot je huidige regelnummer. Zodra je aan het eind van het bestand bent, print je dan gewoon je buffer en klaar ben je. Voorbeeldcode (werking niet gegarandeerd):
Perl:
1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/perl -w
open FILE,"<jouwbestand";
$buf = "";
while (<FILE>) {
  $line = $_;
  if($line eq "ditzoekik\n"){
    $buf = "";
  }
  else {
    $buf .= $line;
  }
}
print $buf;

[ Voor 7% gewijzigd door odysseus op 11-01-2005 22:47 . Reden: programmeerfout hersteld: 'print $buf' buiten de while-lus gezet ]

Leven is het meervoud van lef | In order to make an apple pie from scratch, you must first create the universe.


  • blaataaps
  • Registratie: Juli 2001
  • Niet online
Hmm, inderdaad, ik had het laatste stukje van zn post niet helemaal correct geinterpreteerd zie ik :)

  • TrailBlazer
  • Registratie: Oktober 2000
  • Laatst online: 07-02 09:48

TrailBlazer

Karnemelk FTW

odysseus schreef op dinsdag 11 januari 2005 @ 20:15:
[...]

Volgens mij doet jouw code niet wat de topicstarter wil: bij jou wordt immers alles geprint na de eerste keer dat 'vanafhier' gevonden wordt, en de TS wil nu juist alles hebben vanaf de _laatste_ keer dat de tekst voorkomt. Dat betekent dus feitelijk dat je twee keer door het bestand heen moet of dat je het op een handigere manier aan moet pakken. Zelf zou ik een buffer maken dat alles bevat van de laatste keer dat je de tekst ben tegengekomen tot je huidige regelnummer. Zodra je aan het eind van het bestand bent, print je dan gewoon je buffer en klaar ben je. Voorbeeldcode (werking niet gegarandeerd):
Perl:
1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/perl -w
open FILE,"<jouwbestand";
$buf = "";
while (<FILE>) {
  $line = $_;
  if($line eq "ditzoekik\n"){
    $buf = "";
  }
  else {
    $buf .= $line;
  }
  print $buf;
}
ik denk dat het om een logfile gaat en ik denk niet dat je dat in het geheugen wil opslaan. Ik ben wel eens logfiles tegen gekomen van 500 MB en dat is niet echt fijn

  • Onno
  • Registratie: Juni 1999
  • Niet online
En bovendien print ie $buf na elke regel, lijkt me dat dat na de while moet. :+

  • odysseus
  • Registratie: Augustus 2000
  • Laatst online: 08:56

odysseus

Debian GNU/Linux Sid

Je slaat hier alleen het stuk op tussen twee opeenvolgende matches op - ik neem aan dat dat substantieel minder is dan het hele bestand. Het alternatief zou zijn om het hele bestand eerst door te lopen en elke regel te matchen, zodat je aan het eind weet op welke regel de laatste keer je patroon staat. Vervolgens moet je dan nog een keer het bestand door en alleen alles vanaf die regel printen. Dan heb je dus twee keer je complete logfile doorgeploegd en dat lijkt me ook niet altijd goed. Welk van de twee manieren het beste is, is afhankelijk van hoe vaak het patroon voorkomt in het bestand, de grootte van het bestand en de variatie in afstand tussen twee matches (en ik durf wel te zeggen dat het voor normale logbestanden van bijvoorbeeld maximaal 10MB weinig uitmaakt - het gaat toch wel snel genoeg).

edit:

Die buf stond inderdaad even verkeerd - ik had al gewaarschuwd dat het ongeteste code was :P.

[ Voor 7% gewijzigd door odysseus op 11-01-2005 22:46 ]

Leven is het meervoud van lef | In order to make an apple pie from scratch, you must first create the universe.


Verwijderd

Mits je zeker weet dat de string waar je naar op zoek bent voorkomt (uiteraard makkelijk af te vangen) is het volgende afdoende:

code:
1
tac file | sed '/zoekstring/,99999999d' | tac

Wat doet bovenstaande code?
Draai de regels van het bestand om, vervolgens verwijder je het deel vanaf zoekstring t/m 99999999 regels verder (gewoon een enorm groot getal nemen) en daarna draai je de boel weer om naar de originele volgorde.

[ Voor 7% gewijzigd door Verwijderd op 12-01-2005 01:07 ]


  • pierre-oord
  • Registratie: April 2002
  • Laatst online: 15-01 10:55
Ik open het topic, en zag meer posts dan ik verwachtte ;) ik dacht dat het nogal simpel zou zijn.

Eerst @Kippenijzer: Zelfde probleem blijft, cat pakt dan alle regels

@Niels Klomp: Klinkt erg interessant. Maar wat als zo'n logfile erg groot wordt? Hij kan wel een aantal MB's worden, dan kost dan toch behoorlijk wat kracht, omdraaien, verwijderen, en weer omdraaien, denk ik. Ik zal het iig even proberen, al is het maar als een tijdelijke oplossing.

Anders zou ik het huidige met grep moeten doen, waarbij ik 10x de datum zie maar pas na de laatste datum 1000 regels door grep, zoals ik al geprobeerd had, maar dan zonder de datum te echo-en (ik echo nu "Datum" en daarna op een nieuwe regel de nieuwe datum, leek me handig met terugzoeken in de logfile):
code:
1
2
3
Tue Jan 11 18:52:39 CET 2005
Tue Jan 11 18:53:13 CET 2005
kljfaljda


Of zoals ik nu heb, en dan met grep en een regexp ofzo de datum lijnen verwijderen. Misschien heeft er iemand nog goede ideeën. De file moet eigenlijk vanaf onderen worden ingelezen, en bij de eerste match moet alles vanaf de match naar onderen worden ge-echo-ed.

Ondernemer in tech (oud LOQED.com, nu UpToMore.com)


  • odysseus
  • Registratie: Augustus 2000
  • Laatst online: 08:56

odysseus

Debian GNU/Linux Sid

Werkt mijn oplossing niet? Als je het anders wilt doen, dan kan het volgende ook nog:

code:
1
2
export LINE=`grep -n Datum: mijnbestand | tail -1 | sed s/\:.*$//`
tail -$n mijnbestand | tail +2

Dat werkt hier prima en het is simpel en overzichtelijk. Een klein en waarschijnlijk niet belangrijk probleem is dat het geen atomaire operatie is: je kunt er een keer een regel naast zitten als het bestand verandert tussen de aanroepen van de twee commando's in. Je hebt nu in ieder geval een paar werkende oplossingen - zoek de mooiste maar uit zou ik zeggen :).

En een bestand achterstevoren inlezen kan wel gesimuleerd worden, maar dan moet je denk ik echt gaan programmeren...en de vraag is of het sneller gaat dan het bestand een keer lineair van boven naar beneden doorlopen.

Leven is het meervoud van lef | In order to make an apple pie from scratch, you must first create the universe.


Verwijderd

code:
1
cat bestand | sed -n '/Datum/,$p'


Let op: gebruik enkele quotes in de sed-expressie. Of escapen in geval dubbele quotes.

[ Voor 12% gewijzigd door Verwijderd op 12-01-2005 13:54 ]


  • pierre-oord
  • Registratie: April 2002
  • Laatst online: 15-01 10:55
Ik ga straks de oplossingen proberen; als ik 2x de date laat echo-en, kan ik ook zien welke de snelste is ;) ik laat het horen

edit:
korenwolf, output geeft:
pierre-mik3:/servers/hltv_001# cat screenlog.0 | sed -n '/Datum/,$p'
Datum:
Tue Jan 11 18:52:39 CET 2005
adfasddasfsdafdfDatum:
Tue Jan 11 18:53:13 CET 2005
@ody
Bij jouw stukje "export LINE=`grep -n Datum: mijnbestand | tail -1 | sed s/\:.*$//`"
krijg ik 2 regelnummers terug. 1 teveel? Dan moet ik van dat lijstje er weer 1 gaan maken :X

@niels:
Ik krijg nu terug:
kjfalkjsTue Jan 11 18:53:13 CET 2005

Op 1 regel. Zoekstring was "Datum:".

Hier nog even de file waarmee ik nu werk: Klik
Zie onderaan voor de ge-echode stukjes. Daar moet straks tussen gaan staan wat erboven staat...

Zo even mijn grep manier proberen.
--> Eigen manier toch niet handig, dan zou ik alle datum's moeten weglaten. Ik wil gewoon simpelweg 1 datum zien, en daaronder de tekst. Leuk simpel probleem dit :P

edit:
Zoals je ziet gaat deze log aardig groeien... Dus in het geheugen plaatsen is wat minder handig.

[ Voor 117% gewijzigd door pierre-oord op 12-01-2005 15:31 ]

Ondernemer in tech (oud LOQED.com, nu UpToMore.com)


  • odysseus
  • Registratie: Augustus 2000
  • Laatst online: 08:56

odysseus

Debian GNU/Linux Sid

Ok, volgende poging:

code:
1
2
export LINE=`grep -n ^Datum: mijnbestand | tail -1 | sed s/\:.*$//`
tail -n +$LINE mijnbestand | tail +2

Bugfixes: matcht alleen nog 'Datum:' aan het begin van een regel en gebruikt nu ook in het tweede commando netjes $LINE in plaats van $n zoals ik per ongeluk nog had staan :). Volgens mij doet dit wat je wilt, en anders begrijp ik niet goed wat de bedoeling is :). Het geeft in ieder geval alle regels die volgen op de laatste keer dat er in het bestand een regel zit die begint met 'Datum:'. Mocht je de datum zelf er nog bij willen hebben, dan kan je de laatste 'tail +2' weglaten :).

edit:

Als je bij mijn eerste commando twee resultaten terugkreeg dan was je waarschijnlijk de 'tail -1' in het eerste commando vergeten? Dat geeft namelijk altijd maximaal een resultaat terug :).

[ Voor 17% gewijzigd door odysseus op 12-01-2005 16:25 ]

Leven is het meervoud van lef | In order to make an apple pie from scratch, you must first create the universe.


  • pierre-oord
  • Registratie: April 2002
  • Laatst online: 15-01 10:55
odysseus schreef op woensdag 12 januari 2005 @ 16:20:
Ok, volgende poging:

code:
1
2
export LINE=`grep -n ^Datum: mijnbestand | tail -1 | sed s/\:.*$//`
tail -n +$LINE mijnbestand | tail +2

Bugfixes: matcht alleen nog 'Datum:' aan het begin van een regel en gebruikt nu ook in het tweede commando netjes $LINE in plaats van $n zoals ik per ongeluk nog had staan :). Volgens mij doet dit wat je wilt, en anders begrijp ik niet goed wat de bedoeling is :). Het geeft in ieder geval alle regels die volgen op de laatste keer dat er in het bestand een regel zit die begint met 'Datum:'. Mocht je de datum zelf er nog bij willen hebben, dan kan je de laatste 'tail +2' weglaten :).

edit:

Als je bij mijn eerste commando twee resultaten terugkreeg dan was je waarschijnlijk de 'tail -1' in het eerste commando vergeten? Dat geeft namelijk altijd maximaal een resultaat terug :).
Ik ga even proberen, stay tuned ;) en bedankt. IN je eerste commando heb ik tail -1 volgens mij door een L vervangen :X ik maakte niet zomaar een copy paste, maar probeer ook nog een beetje te begrijpen wat het doet ;)

edit:
Werkt perfect!

In de screenfile (zie vorige link) miste een Datum: plaatje, niet zo netjes van me dat maakte me iets in de war. En die -l vergissing is ook niet handig. De #iets fout had ik wel door ;)

Het werkt perfect:
eerst greppen waar Datum aan het begin van de regel staat, daarna (waar mijn fout zat) alleen de laatste regel lezen.
Wat sed doet zit ik ff te kijken, een soort van regexp achtig iets, maar het haalt iig Datum: na het regelnummer weg.

Hierna greppen op het regelnummer en klaar, heel makkelijk.

Eigenlijk zou tail een soort ingebouwde grep moeten hebben, maar dit werkt perfect. Dat vind ik wel mooi aan linux, de kracht van de shell, dos is er niets mee ;)

[ Voor 25% gewijzigd door pierre-oord op 12-01-2005 17:15 ]

Ondernemer in tech (oud LOQED.com, nu UpToMore.com)


  • odysseus
  • Registratie: Augustus 2000
  • Laatst online: 08:56

odysseus

Debian GNU/Linux Sid

pierre-oord schreef op woensdag 12 januari 2005 @ 16:59:
Wat sed doet zit ik ff te kijken, een soort van regexp achtig iets, maar het haalt iig Datum: na het regelnummer weg.
De gevonden regel zal eerst een regelnummer hebben, dan een dubbele punt en dan de inhoud van de regel. Het doel is om alleen het regelnummer over te houden
De reguliere expressie 'sed s/\:.*$//' betekent min of meer het volgende:
• 's' staat voor substitute, vervangen dus. de syntax is 's/vinddit/vervangdoordit/'
• Het te vinden patroon is '\:.*$'. De '\:' staat voor de dubbele punt. De '.' staat voor een willekeurig teken en '*' betekent dat het voorgaande teken (hier dus een willekeurig teken) nul of meer keer mag voorkomen. '$' staat voor het regeleinde.
• Dat patroon matcht dus alles vanaf de eerst gevonden dubbele punt tot aan het eind van de regel.
• Het gematchte deel moet vervangen worden door niets: aangezien er niets is opgegeven bij 'vervangdoordit' gebeurt dat ook netjes.
• Het resultaat is dus dat alleen het stuk van het begin van de regel tot net voor de eerste dubbele punt overblijft en dat is exact het regelnummer dat je wilde hebben :).

De 'tail -n +$LINE' betekent dat alles geprint zal worden vanaf het regelnummer dat in $LINE wordt gegeven :).

Leven is het meervoud van lef | In order to make an apple pie from scratch, you must first create the universe.


  • pierre-oord
  • Registratie: April 2002
  • Laatst online: 15-01 10:55
odysseus schreef op woensdag 12 januari 2005 @ 21:14:
[...]

De gevonden regel zal eerst een regelnummer hebben, dan een dubbele punt en dan de inhoud van de regel. Het doel is om alleen het regelnummer over te houden
De reguliere expressie 'sed s/\:.*$//' betekent min of meer het volgende:
• 's' staat voor substitute, vervangen dus. de syntax is 's/vinddit/vervangdoordit/'
• Het te vinden patroon is '\:.*$'. De '\:' staat voor de dubbele punt. De '.' staat voor een willekeurig teken en '*' betekent dat het voorgaande teken (hier dus een willekeurig teken) nul of meer keer mag voorkomen. '$' staat voor het regeleinde.
• Dat patroon matcht dus alles vanaf de eerst gevonden dubbele punt tot aan het eind van de regel.
• Het gematchte deel moet vervangen worden door niets: aangezien er niets is opgegeven bij 'vervangdoordit' gebeurt dat ook netjes.
• Het resultaat is dus dat alleen het stuk van het begin van de regel tot net voor de eerste dubbele punt overblijft en dat is exact het regelnummer dat je wilde hebben :).

De 'tail -n +$LINE' betekent dat alles geprint zal worden vanaf het regelnummer dat in $LINE wordt gegeven :).
bedankt voor de info. idd een soort regexp, als ik het met php probeer te vergelijken preg_match enzo, maar iets anders. Tail begreep ik wel met + en - :) . Blijkt toch maar weer dat het best simpel is zo'n veel gedoe het te doen, maar je moet er maar even opkomen ;)

Hardstikke bedankt!

Ondernemer in tech (oud LOQED.com, nu UpToMore.com)

Pagina: 1