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
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 å matchefoo
og gjør det som kommer inn i squigglies til de matchende linjene. Erstattfoo
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 medbar
. 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 tilstart
-etiketten tidligere for å fortsette å legge til neste linje så lenge mønsterområdet ikke inneholderbar
. -
/your_regex/p
skriver ut mønsterområdet hvis det samsvarer medyour_regex
. Du bør erstatteyour_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 derdef
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 atsecond_line_word
er på tredje linje, så savner du ikke bare den første linjen (pga. ditt andregrep
), 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 variabelenall
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
-
/foo/{
utløses når linjen inneholder «foo» -
n
erstatt mønsterområdet med neste linje, dvs. ordet «her» -
b gotoloop
gren til etiketten «gotoloop» -
:gotoloop
definerer etiketten «gotoloop» -
/bar/!{
hvis mønsteret ikke inneholder «bar» -
h
erstatt holdplassen med mønster, så «her» lagres i holdeplassen -
b loop
gren til etiketten «loop» -
:loop
definerer etiketten «loop» -
N
legger mønsteret til holdeplassen.
Holdeplassen inneholder nå:
«her»
«er» -
:gotoloop
Vi er nå i trinn 4, og sløyfer til en linje inneholder «bar» -
/bar/
loop er ferdig, «bar» er funnet, det » s mønsterområdet - mønsterplass erstattes med hold mellomrom som inneholder alle linjene mellom» foo «og» bar «som er lagret i løpet av hovedsløyfen
-
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.
grep
. De er tett beslektede, men ikke dups, IMO."grep"
og antydet verbet » til grep «, og topp svar, inkludert akseptert, ikke bruk ‘ t grep.