Det virker som om jeg misbruker grep / egrep.

Jeg prøvde å søke etter strenger i flere linjer og kunne ikke finne en kamp mens jeg vet at det jeg ser etter skulle stemme overens. Opprinnelig trodde jeg at regexene mine var feil, men til slutt leste jeg at disse verktøy fungerer per linje (også regexene mine var så trivielle at det ikke kunne være problemet).

Så hvilket verktøy vil man bruke til å søke etter mønstre på tvers av flere linjer?

Kommentarer

  • mulig duplikat av Multiline mønster match ved bruk av sed, awk eller grep
  • @CiroSantilli – Jeg tror ikke at denne Q og den du koblet til er duplikater. Den andre Q spør hvordan du ‘ d matcher mønstre med flere linjer (dvs. hvilket verktøy skal / kan jeg bruker å gjøre dette) mens denne spør hvordan du gjør dette med grep. De er tett beslektede, men ikke dups, IMO.
  • @sim de tilfellene er vanskelig å bestemme: Jeg kan se poenget ditt. Jeg tror akkurat denne saken er bedre som et duplikat fordi se brukeren sa "grep" og antydet verbet » til grep «, og topp svar, inkludert akseptert, ikke bruk ‘ t grep.
  • Det er ingen indikasjoner som viser at et reguleringsuttrykk med flere linjer er nødvendig her. Vennligst vurder å vise et faktisk eksempel med inndata og forventede utdata, så vel som din tidligere innsats.

Svar

Her «sa sed en som vil gi deg grep -lignende oppførsel på tvers av flere linjer:

sed -n "/foo/{:start /bar/!{N;b start};/your_regex/p}" your_file 

Slik fungerer det

  • -n undertrykker standard oppførsel for å skrive ut hver linje
  • /foo/{} instruerer den til å matche foo og gjør det som kommer inn i squigglies til de matchende linjene. Erstatt foo med startdelen av mønsteret.
  • :start er en forgreningsetikett som hjelper oss å fortsette å løpe til vi finner slutten på regexen vår.
  • /bar/!{} vil utføre hva som er i squigglies til linjene som ikke samsvarer med bar. Erstatt med slutten av mønsteret.
  • N legger neste linje til den aktive bufferen (sed kaller dette mønsterrommet)
  • b start vil ubetinget forgrene seg til start -etiketten tidligere for å fortsette å legge til neste linje så lenge mønsterområdet ikke inneholder bar.
  • /your_regex/p skriver ut mønsterområdet hvis det samsvarer med your_regex. Du bør erstatte your_regex med hele uttrykket du vil matche på tvers av flere linjer.

Kommentarer

  • +1 Legger dette til toolikt! Takk.
  • Merk: På MacOS gir dette sed: 1: "/foo/{:start /bar/!{N;b ...": unexpected EOF (pending }'s)
  • Å få sed: unterminated { feil
  • @Nomaed Skutt i mørket her, men inneholder tilfeldigvis din regex » {» tegn? Hvis ja, må du ‘ tilbakeslag-unnslippe dem.
  • @Nomaed Det ser ut til at det har å gjøre med forskjellene mellom sed implementeringer. Jeg prøvde å følge anbefalingene i det svaret for å gjøre skriptet ovenfor standardkompatibelt, men det fortalte meg at » start » var en udefinert merkelapp. Så jeg ‘ er ikke sikker på om dette kan gjøres på en standardkompatibel måte. Hvis du klarer det, kan du gjerne redigere svaret mitt.

Svar

Jeg bruker vanligvis et verktøy kalt pcregrep som kan installeres i det meste av linux-smaken ved hjelp av yum eller apt.

For eksempel.

Anta at hvis du har en fil som heter testfile med innhold

abc blah blah blah def blah blah blah 

Du kan kjøre følgende kommando:

$ pcregrep -M "abc.*(\n|.)*def" testfile 

for å gjøre mønstermatching på tvers av flere linjer.

Videre du kan gjøre det samme med sed også.

$ sed -e "/abc/,/def/!d" testfile 

Kommentarer

  • dette sed forslaget hopper linjen der def ville bli funnet

Svar

Bare en normal grep som støtter Perl-regexp parameter P vil gjøre denne jobben.

$ echo "abc blah blah blah def blah blah blah" | grep -oPz "(?s)abc.*?def" abc blah blah blah def 

(?s) kalt DOTALL-modifikator som gjør at punkt i regexen din ikke bare samsvarer med tegnene, men også linjeskiftene.

Kommentarer

  • Når jeg prøver denne løsningen, slutter ikke utgangen på ‘ def ‘ men går til slutten av filen ‘ blah ‘
  • kanskje grep støtter ikke -P alternativet
  • Dette var det eneste som fungerte for meg – prøvde alle sed forslag, men gikk ikke ‘ til å installere grep-alternativer.
  • $ grep --version: grep (GNU grep) 3.1 i Windows Git Bash har et alternativ -P, --perl-regexp men (?s) ikke ‘ virker ikke der. Den viser fortsatt bare første linje. Det samme mønsteret med samme teststreng fungerer på regex101.com . Er det et alternativ i Git Bash? sed? (sed (GNU sed) 4.8 her)
  • Vet du hvordan du legger til kontekst i utgangen? grep -1 fungerer ikke ‘ her.

Svar

Her «er en enklere tilnærming ved bruk av Perl:

perl -e "$f=join("",<>); print $& if $f=~/foo\nbar.*\n/m" file 

eller (siden JosephR tok sed rute , jeg stjeler skamløst hans forslag )

perl -n000e "print $& while /^foo.*\nbar.*\n/mg" file 

### Forklaring

$f=join("",<>);: dette leser hele filen og lagrer innholdet (nye linjer og alt) i variabelen $f. Vi prøver deretter å matche foo\nbar.*\n, og skrive den ut hvis den stemmer overens (den spesielle variabelen $& inneholder den siste funnet). ///m er nødvendig for å få det vanlige uttrykket til å matche på tvers av nye linjer.

-0 angir inngangspostseparatoren. Hvis du setter dette til 00, aktiveres «avsnittemodus» der Perl vil bruke påfølgende nye linjer (\n\n) som postutskiller. I tilfeller der det ikke er noen påfølgende nye linjer, blir hele filen lest (slurpet) samtidig.

### Advarsel: Gjør ikke dette for store filer, den lastes inn hele filen i minnet, og det kan være et problem.

Kommentarer

  • Jeg don ‘ t vet mye om Perl, men ville det ‘ ikke trenge å være my $f=join("",<>);, strengt tatt?
  • Bare @Sapphire_Brick hvis du er i streng modus (use strict;). Det ‘ er en god vane å komme inn på, spesielt når du skriver større manus, men det ‘ s overkill for en liten en-liner som dette en.

Svar

Anta at vi har filen test.txt inneholder:

blabla blabla foo here is the text to keep between the 2 patterns bar blabla blabla 

Følgende kode kan brukes:

sed -n "/foo/,/bar/p" test.txt 

For følgende utgang:

foo here is the text to keep between the 2 patterns bar 

Svar

Grep-alternativet sift støtter flerlinjematching (ansvarsfraskrivelse: Jeg er forfatteren).

Anta testfile inneholder:

 <book> <title>Lorem Ipsum</title> <description>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua</description> </book> 

sift -m '<description>.*?</description>' (vis linjene som inneholder beskrivelsen)

Resultat:

 testfile: <description>Lorem ipsum dolor sit amet, consectetur testfile: adipiscing elit, sed do eiusmod tempor incididunt ut testfile: labore et dolore magna aliqua</description> 

sift -m '<description>(.*?)</description>' --replace 'description="$1"' --no-filename (trekk ut og formater beskrivelsen)

Resultat:

description="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" 

Kommentarer

  • Veldig fint verktøy. Gratulerer! Prøv å inkludere det i distribusjoner som Ubuntu.

Svar

Jeg løste denne for meg ved hjelp av grep og – Et alternativ med en annen grep.

grep first_line_word -A 1 testfile | grep second_line_word 

Alternativet -A 1 skriver ut 1 linje etter den funnet linjen. Selvfølgelig avhenger det av fil- og ordkombinasjon. Men for meg var det den raskeste og mest pålitelige løsningen.

Kommentarer

  • alias grepp = ‘ grep –farge = auto -B10 -A20 -i ‘ så katt somfil | grepp bla | grepp foo | grepp bar … ja de -A og -B er veldig praktiske …du har det beste svaret
  • Dette er ‘ t superdeterministisk, og det ignorerer hele mønsteret til fordel for å bare få en annen enkelt linje (bare basert på dens nærhet til første linje). ‘ er bedre å fortelle programmet å gå så langt det trenger å gå for å komme til et slags mønster du ‘ helt sikker er slutten på teksten du ‘ prøver å matche. For eksempel hvis testfile blir oppdatert slik at second_line_word er på tredje linje, så savner du ikke bare den første linjen (pga. ditt andre grep), men du ‘ mangler ikke linjen som begynte å vises mellom de to.
  • Dette ville være en god nok MO for ad hoc-kommandoer der du egentlig bare vil ha en enkelt linje i produksjonen du allerede har forstått. Jeg tror ikke ‘ at ‘ er hva OP er etter, og du kan sannsynligvis også bare kopiere / lime inn på det tidspunktet pga. det er ad hoc.

Svar

En måte å gjøre dette på er med Perl. f.eks. her er innholdet i en fil som heter foo:

foo line 1 bar line 2 foo foo foo line 5 foo bar line 6 

Nå, her er noen Perl som vil match mot en linje som begynner med foo etterfulgt av en linje som begynner med bar:

cat foo | perl -e "while(<>){$all .= $_} while($all =~ /^(foo[^\n]*\nbar[^\n]*\n)/m) { print $1; $all =~ s/^(foo[^\n]*\nbar[^\n]*\n)//m; }" 

Perl, fordelt:

  • while(<>){$all .= $_} Dette laster hele standardinngangen inn til variabelen $all
  • while($all =~ Mens variabelen all har det regulære uttrykket …
  • /^(foo[^\n]*\nbar[^\n]*\n)/m Regex: foo i begynnelsen av linjen, etterfulgt av et hvilket som helst antall ikke-nye linjer, etterfulgt av en ny linje, etterfulgt umiddelbart av «bar», og resten av linjen med bar i. /m på slutten av regex betyr «match over flere linjer»
  • print $1 Skriv ut delen av regex som var i parentes (i dette tilfellet hele regulært uttrykk)
  • s/^(foo[^\n]*\nbar[^\n]*\n)//m Slett den første kampen for regex, slik at vi kan matche flere tilfeller av regex i den aktuelle filen

Og utdata:

foo line 1 bar line 2 foo bar line 6 

Kommentarer

  • Bare innom for å si at Perl kan forkortes til det mer idiomatiske: perl -n0777E 'say $& while /^foo.*\nbar.*\n/mg' foo

Svar

Hvis vi ønsker å få teksten mellom de to mønstrene eksklusivt.

Tenk at vi har filen test.txt inneholder:

blabla blabla foo here is the text to keep between the 2 patterns bar blabla blabla 

Følgende kode kan brukes:

 sed -n "/foo/{ n b gotoloop :loop N :gotoloop /bar/!{ h b loop } /bar/{ g p } }" test.txt 

For følgende utgang:

here is the text to keep between the 2 patterns 

Hvordan fungerer det, la «s gjør det trinn for trinn

  1. /foo/{ utløses når linjen inneholder «foo»
  2. n erstatt mønsterområdet med neste linje, dvs. ordet «her»
  3. b gotoloop gren til etiketten «gotoloop»
  4. :gotoloop definerer etiketten «gotoloop»
  5. /bar/!{ hvis mønsteret ikke inneholder «bar»
  6. h erstatt holdplassen med mønster, så «her» lagres i holdeplassen
  7. b loop gren til etiketten «loop»
  8. :loop definerer etiketten «loop»
  9. N legger mønsteret til holdeplassen.
    Holdeplassen inneholder nå:
    «her»
    «er»
  10. :gotoloop Vi er nå i trinn 4, og sløyfer til en linje inneholder «bar»
  11. /bar/ loop er ferdig, «bar» er funnet, det » s mønsterområdet
  12. mønsterplass erstattes med hold mellomrom som inneholder alle linjene mellom» foo «og» bar «som er lagret i løpet av hovedsløyfen
  13. p kopier mønsterplass til standard utdata

Ferdig!

Kommentarer

  • Bra gjort, +1. Jeg unngår vanligvis å bruke disse kommandoene ved å tr ‘ inn nye linjene i SOH og utføre normale sed-kommandoer og erstatte de nye linjene.

Legg igjen en kommentar

Din e-postadresse vil ikke bli publisert. Obligatoriske felt er merket med *