Det ser ud til, at jeg misbruger grep
/ egrep
.
Jeg forsøgte at søge efter strenge i flere linjer og kunne ikke finde et match, mens jeg ved, at det, jeg leder efter, skulle matche. Oprindeligt troede jeg, at mine regexes var forkert, men til sidst læste jeg, at disse værktøjer fungerer pr. linje (også mine regexer var så trivielle, at det ikke kunne være problemet).
Så hvilket værktøj vil man bruge til at søge mønstre på tværs af flere linjer?
Kommentarer
Svar
Her “sa sed
en, der giver dig grep
-lignende adfærd på tværs af flere linjer:
sed -n "/foo/{:start /bar/!{N;b start};/your_regex/p}" your_file
Sådan fungerer det
-
-n
undertrykker standardopførelsen ved udskrivning af hver linje -
/foo/{}
instruerer den til at matchefoo
og gør hvad der kommer inden i squigglies til de matchende linjer. Udskiftfoo
med startdelen af mønsteret. -
:start
er en forgreningsetiket, der hjælper os med at holde løkke, indtil vi finder slutningen på vores regex. -
/bar/!{}
udfører, hvad der er i squigglies til de linjer, der ikke matcherbar
. Udskift med slutningen af mønsteret. -
N
føjer den næste linje til den aktive buffer (sed
kalder dette mønsterområdet) -
b start
vil ubetinget forgrene sig tilstart
-mærket tidligere for at fortsætte med at tilføje den næste linje, så længe mønsterområdet ikke indeholderbar
. -
/your_regex/p
udskriver mønsterområdet, hvis det matcheryour_regex
. Du skal erstatteyour_regex
med hele det udtryk, du vil matche på tværs af flere linjer.
Kommentarer
- +1 Tilføjelse af dette til toolikt! Tak.
- Bemærk: På MacOS giver dette
sed: 1: "/foo/{:start /bar/!{N;b ...": unexpected EOF (pending }'s)
- At få
sed: unterminated {
fejl - @Nomaed Skudt i mørke her, men indeholder din regex tilfældigvis ” {” tegn? Hvis det er tilfældet, skal du ‘ nødt til at backslash-undslippe dem.
- @Nomaed Det ser ud til at det har at gøre med forskellene mellem
sed
implementeringer. Jeg forsøgte at følge anbefalingerne i det svar for at gøre ovenstående script standardkompatibelt, men det fortalte mig, at ” start ” var en udefineret etiket. Så jeg ‘ er ikke sikker på, om dette kan gøres på en standardkompatibel måde. Hvis du administrerer det, er du velkommen til at redigere mit svar.
Svar
Jeg bruger generelt et værktøj kaldes pcregrep
, som kan installeres i det meste af linux-smag ved hjælp af yum
eller apt
.
For eksempel.
Antag, at hvis du har en fil med navnet testfile
med indhold
abc blah blah blah def blah blah blah
Du kan køre følgende kommando:
$ pcregrep -M "abc.*(\n|.)*def" testfile
for at lave mønstermatchning på tværs af flere linjer.
Desuden du kan også gøre det med sed
.
$ sed -e "/abc/,/def/!d" testfile
Kommentarer
- dette
sed
forslag springes over linjen, hvordef
findes
Svar
Simpelthen en normal grep, der understøtter Perl-regexp
parameter P
vil udføre dette job.
$ echo "abc blah blah blah def blah blah blah" | grep -oPz "(?s)abc.*?def" abc blah blah blah def
(?s)
kaldes DOTALL-modifikator, der gør prikken i din regex til at matche ikke kun tegnene, men også linjeskiftene.
Kommentarer
- Når jeg prøver denne løsning, slutter output ikke ved ‘ def ‘ men går til slutningen af filen ‘ blah ‘
- måske din grep understøtter ikke
-P
mulighed - Dette var det eneste, der fungerede for mig – prøvede alle
sed
forslag, men gik ‘ ikke så langt som at installere grep-alternativer. -
$ grep --version
:grep (GNU grep) 3.1
i Windows Git Bash har en indstilling-P, --perl-regexp
men(?s)
betyder ikke ‘ virker ikke der. Det viser stadig kun den første linje. Det samme mønster med den samme teststreng fungerer på regex101.com . Er der et alternativ i Git Bash?sed
? (sed (GNU sed) 4.8
her) - Ved du hvordan du tilføjer kontekst til output? grep -1 fungerer ikke ‘ her.
Svar
Her er “en enklere tilgang med Perl:
perl -e "$f=join("",<>); print $& if $f=~/foo\nbar.*\n/m" file
eller (da JosephR tog sed
rute , jeg stjæler skamløst hans forslag )
perl -n000e "print $& while /^foo.*\nbar.*\n/mg" file
### Forklaring
$f=join("",<>);
: dette læser hele filen og gemmer dens indhold (nye linjer og alt) i variablen $f
. Vi forsøger derefter at matche foo\nbar.*\n
og udskrive det, hvis det matcher (den specielle variabel $&
holder den sidst fundne match). ///m
er nødvendig for at få det regulære udtryk til at matche på tværs af nye linjer.
-0
indstiller input-record-separator. Hvis du indstiller dette til 00
, aktiveres “afsnitstilstand”, hvor Perl vil bruge fortløbende nye linjer (\n\n
) som pladeseparator. I tilfælde, hvor der ikke er nogen på hinanden følgende nye linjer, læses hele filen (slurpes) på én gang.
### Advarsel: Gør ikke dette for store filer, den indlæses hele filen i hukommelsen, og det kan være et problem.
Kommentarer
- Jeg don ‘ t ved meget om Perl, men ville det ‘ t være nødvendigt at være
my $f=join("",<>);
strengt taget? - kun @Sapphire_Brick hvis du er i streng tilstand (
use strict;
). Det ‘ er en god vane at komme ind på, især når du skriver større scripts, men det ‘ s overoverførsel til en lille en-liner som denne en.
Svar
Antag, at vi har filen test.txt indeholdende:
blabla blabla foo here is the text to keep between the 2 patterns bar blabla blabla
Følgende kode kan bruges:
sed -n "/foo/,/bar/p" test.txt
For følgende output:
foo here is the text to keep between the 2 patterns bar
Svar
Grep-alternativet sift understøtter multiline matching (ansvarsfraskrivelse: Jeg er forfatteren).
Antag testfile
indeholder:
<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>'
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
(uddrag 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
- Meget flot værktøj. Tillykke! Forsøg at medtage det i distributioner som Ubuntu.
Svar
Jeg løste denne for mig ved hjælp af grep og – En indstilling med en anden grep.
grep first_line_word -A 1 testfile | grep second_line_word
Indstillingen -A 1 udskriver 1 linje efter den fundne linje. Naturligvis afhænger det af din fil og ordkombination. Men for mig var det den hurtigste og mest pålidelige løsning.
Kommentarer
- alias grepp = ‘ grep –farve = auto -B10 -A20 -i ‘ derefter kat somefile | grepp bla | grepp foo | grepp bar … ja de -A og -B er meget praktiske …du har det bedste svar
- Dette er ikke ‘ t super deterministisk, og det ignorerer hele mønsteret til fordel for bare at få en anden enkelt linje (bare baseret på dens nærhed til første linje). ‘ er bedre at fortælle programmet at gå, hvor langt det skal gå for at komme til en slags mønster, du ‘ helt sikker er slutningen af den tekst, du ‘ prøver at matche. For eksempel, hvis
testfile
opdateres således, atsecond_line_word
er på tredje linje, så mangler du ikke kun nu den første linje (pga. dit andetgrep
) men du ‘ mangler ikke den linje, der begyndte at vises mellem de to. - Dette ville være en god nok MO til ad hoc-kommandoer, hvor du virkelig bare vil have en enkelt linje i output, som du allerede har forstået. Jeg tror ikke ‘ at ‘ er hvad OP er efter, og du kunne sandsynligvis også bare kopiere / indsætte på det tidspunkt pga. det er ad hoc.
Svar
En måde at gøre dette på er med Perl. for eksempel. her “er indholdet af en fil med navnet foo
:
foo line 1 bar line 2 foo foo foo line 5 foo bar line 6
Her er her nogle Perl, som vil match mod enhver linje, der begynder med foo efterfulgt af enhver linje, der begynder 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, opdelt:
-
while(<>){$all .= $_}
Dette indlæser hele standardindgangen til variablen$all
-
while($all =~
Mens variablenall
har det regulære udtryk … -
/^(foo[^\n]*\nbar[^\n]*\n)/m
Regex: foo i begyndelsen af linjen efterfulgt af et vilkårligt antal ikke-nylinjetegn, efterfulgt af en ny linje, efterfulgt straks af “bjælke” og resten af linjen med bjælke i den./m
i slutningen af regex betyder “match på tværs af flere linjer” -
print $1
Udskriv den del af regex der var i parentes (i dette tilfælde hele det regulære udtryk) -
s/^(foo[^\n]*\nbar[^\n]*\n)//m
Slet det første match for regex, så vi kan matche flere tilfælde af regex i den pågældende fil
Og output:
foo line 1 bar line 2 foo bar line 6
Kommentarer
- Bare kom forbi for at sige, at din Perl kan afkortes til det mere idiomatiske:
perl -n0777E 'say $& while /^foo.*\nbar.*\n/mg' foo
Svar
Hvis vi ønsker at få teksten mellem de to mønstre eksklusive sig selv.
Antag at vi har filen test.txt indeholdende:
blabla blabla foo here is the text to keep between the 2 patterns bar blabla blabla
Følgende kode kan bruges:
sed -n "/foo/{ n b gotoloop :loop N :gotoloop /bar/!{ h b loop } /bar/{ g p } }" test.txt
For følgende output:
here is the text to keep between the 2 patterns
Hvordan fungerer det, lad “s gør det trin for trin
-
/foo/{
udløses, når linjen indeholder “foo” -
n
udskift mønsterområdet med næste linje, dvs. ordet “her” -
b gotoloop
gren til etiketten “gotoloop” -
:gotoloop
definerer etiketten “gotoloop” -
/bar/!{
hvis mønsteret ikke indeholder “bar” -
h
udskift holdrummet med mønster, så “her” gemmes i holdrummet -
b loop
gren til etiketten “loop” -
:loop
definerer etiketten “loop” -
N
tilføjer mønsteret til holdrummet.
Holdrummet indeholder nu:
“her”
“er” -
:gotoloop
Vi er nu i trin 4 og sløjfer, indtil en linje indeholder “bar” -
/bar/
loop er færdig, “bar” er fundet, det ” s mønsterområdet - mønsterplads erstattes med holdrum, der indeholder alle linjerne mellem” foo “og” bar “, der er gemt under hovedsløjfen
-
p
kopier mønsterplads til standardoutput
Udført!
Kommentarer
- Godt gået, +1. Jeg undgår normalt at bruge disse kommandoer ved at tr ‘ indføre de nye linjer i SOH og udføre normale sed-kommandoer og derefter erstatte de nye linjer.
grep
. De er tæt beslægtede, men ikke dups, IMO."grep"
foreslår verbet ” til grep “, og top svar, inklusive accepteret, brug ikke ‘ t brug grep.