Det verkar som att jag missbrukar grep
/ egrep
.
Jag försökte söka efter strängar i flera rader och kunde inte hitta en matchning medan jag vet att det jag letar efter skulle matcha. Ursprungligen trodde jag att mina regexer var fel men så småningom läste jag att dessa verktyg fungerar per rad (även mina regexer var så triviala att det inte kunde vara problemet).
Så vilket verktyg skulle man använda för att söka mönster över flera rader?
Kommentarer
Svar
Här ”sa sed
en som ger dig grep
-liknande beteende över flera rader:
sed -n "/foo/{:start /bar/!{N;b start};/your_regex/p}" your_file
Så fungerar det
-
-n
undertrycker standardbeteendet för att skriva ut varje rad -
/foo/{}
instruerar att den ska matchafoo
och gör vad som kommer inuti squiggliesna till de matchande raderna. Byt utfoo
med den inledande delen av mönstret. -
:start
är en förgreningsetikett som hjälper oss att fortsätta att slingra tills vi hittar slutet på vår regex. -
/bar/!{}
kommer att utföra vad som finns i squigglies till raderna som inte matcharbar
. Ersätt med slutet av mönstret. -
N
lägger till nästa rad till den aktiva bufferten (sed
kallar detta mönsterutrymmet) -
b start
förgrenar sig villkorslöst tillstart
-etiketten tidigare för att fortsätta lägga till nästa rad så länge som mönsterutrymmet inte innehållerbar
. -
/your_regex/p
skriver ut mönsterutrymmet om det matcharyour_regex
. Du bör ersättayour_regex
med hela uttrycket du vill matcha över flera rader.
Kommentarer
- +1 Lägga till detta i toolikt! Tack.
- Obs! På MacOS ger detta
sed: 1: "/foo/{:start /bar/!{N;b ...": unexpected EOF (pending }'s)
- Att få
sed: unterminated {
fel - @Nomaed Skott i mörkret här, men innehåller din regex någon ” {” tecken? Om så är fallet måste du ’ slå tillbaka dem.
- @Nomaed Det verkar som om det har att göra med skillnaderna mellan
sed
implementeringar. Jag försökte följa rekommendationerna i det svaret för att göra ovanstående skript standardkompatibelt men det berättade för mig att ” start ” var en odefinierad märka. Så jag ’ är inte säker på om detta kan göras på ett standardkompatibelt sätt. Om du klarar det är du välkommen att redigera mitt svar.
Svar
Jag använder vanligtvis ett verktyg kallas pcregrep
som kan installeras i större delen av linux-smaken med yum
eller apt
.
Till exempel.
Anta om du har en fil med namnet testfile
med innehåll
abc blah blah blah def blah blah blah
Du kan köra följande kommando:
$ pcregrep -M "abc.*(\n|.)*def" testfile
för att göra mönstermatchning över flera rader.
Dessutom, du kan göra detsamma med sed
också.
$ sed -e "/abc/,/def/!d" testfile
Kommentarer
- detta
sed
förslaget hoppar raden därdef
skulle hittas
Svar
Helt enkelt en normal grep som stöder Perl-regexp
parameter P
gör det här jobbet.
$ echo "abc blah blah blah def blah blah blah" | grep -oPz "(?s)abc.*?def" abc blah blah blah def
(?s)
kallas DOTALL-modifierare vilket gör att punkten i din regex inte bara matchar tecknen utan också radbrytningarna.
Kommentarer
- När jag försöker med den här lösningen slutar inte utdata på ’ def ’ men går till slutet av filen ’ blah ’
- kanske din grep stöder inte
-P
-alternativ - Detta var det enda som fungerade för mig – försökte alla
sed
förslag, men gick inte ’ så långt som att installera grep-alternativ. -
$ grep --version
:grep (GNU grep) 3.1
i Windows Git Bash har ett alternativ-P, --perl-regexp
men(?s)
inte ’ verkar inte fungera där. Den visar fortfarande endast första raden. Samma mönster med samma teststräng fungerar på regex101.com . Finns det ett alternativ i Git Bash?sed
? (sed (GNU sed) 4.8
här) - Vet du hur du lägger till kontext till utgången? grep -1 fungerar inte ’ här.
Svar
Här ”är en enklare metod med Perl:
perl -e "$f=join("",<>); print $& if $f=~/foo\nbar.*\n/m" file
eller (eftersom JosephR tog sed
rutt , jag stjäl skamlöst hans förslag )
perl -n000e "print $& while /^foo.*\nbar.*\n/mg" file
### Förklaring
$f=join("",<>);
: detta läser hela filen och sparar dess innehåll (nya rader och allt) i variabeln $f
. Vi försöker sedan matcha foo\nbar.*\n
och skriva ut det om det matchar (specialvariabeln $&
innehåller den senaste matchningen som hittades). ///m
behövs för att det reguljära uttrycket ska matcha över nya rader.
-0
ställer in ingångspostseparatorn. Om du ställer in detta på 00
aktiveras ”avsnittsläge” där Perl kommer att använda efterföljande nya rader (\n\n
) som postavgränsare. I fall där det inte finns några nya rader i följd, läses hela filen (slurpas) på en gång.
### Varning: Gör inte detta för stora filer, det laddas hela filen i minnet och det kan vara ett problem.
Kommentarer
- Jag don ’ t vet mycket om Perl, men skulle ’ inte behöva vara
my $f=join("",<>);
, strikt taget? - endast @Sapphire_Brick om du är i strikt läge (
use strict;
). Det ’ är en bra vana att komma in i, särskilt när man skriver större manus, men det ’ s överdöd för en liten enfodral som denna en.
Svar
Antag att vi har filen test.txt innehållande:
blabla blabla foo here is the text to keep between the 2 patterns bar blabla blabla
Följande kod kan användas:
sed -n "/foo/,/bar/p" test.txt
För följande utdata:
foo here is the text to keep between the 2 patterns bar
Svar
Grep-alternativet sift stöder multilinjematchning (ansvarsfriskrivning: Jag är författaren).
Antag att testfile
innehåller:
<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>'
(visa raderna som innehåller beskrivningen)
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
(extrahera och formatera beskrivningen)
Resultat:
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
Kommentarer
- Mycket trevligt verktyg. Grattis! Försök att inkludera det i distributioner som Ubuntu.
Svar
Jag löste den här för mig med grep och – Ett alternativ med en annan grep.
grep first_line_word -A 1 testfile | grep second_line_word
Alternativet -A 1 skriver ut en rad efter den hittade raden. Naturligtvis beror det på din fil- och ordkombination. Men för mig var det den snabbaste och pålitligaste lösningen.
Kommentarer
- alias grepp = ’ grep –färg = auto -B10 -A20 -i ’ sedan kattfil | grepp bla | grepp foo | grepp bar … ja de -A och -B är väldigt praktiska …du har det bästa svaret
- Det här är ’ t superdeterministiskt och det ignorerar hela mönstret till förmån för att bara få en annan enstaka rad (bara baserat på dess närhet till första raden). Det ’ är bättre att säga till programmet att gå hur långt det än måste gå för att komma till ett slags mönster du ’ helt säker är slutet på texten som du ’ försöker matcha. Till exempel om
testfile
uppdateras så attsecond_line_word
står på tredje raden, saknar du inte bara den första raden (på grund av din andragrep
) men du ’ saknar inte raden som började visas mellan de två. - Detta skulle vara tillräckligt bra MO för ad hoc-kommandon där du verkligen bara vill ha en enda rad i utdata som du redan förstod. Jag tror inte ’ att ’ är vad OP är efter och du kan antagligen också bara kopiera / klistra in vid den tidpunkten på grund av det är ad hoc.
Svar
Ett sätt att göra detta är med Perl. t.ex. här är innehållet i en fil med namnet foo
:
foo line 1 bar line 2 foo foo foo line 5 foo bar line 6
Nu, här är lite Perl som kommer matcha alla rader som börjar med foo följt av alla rader som börjar 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, uppdelad:
-
while(<>){$all .= $_}
Detta laddar in hela standardingången till variabeln$all
-
while($all =~
Medan variabelnall
har det reguljära uttrycket … -
/^(foo[^\n]*\nbar[^\n]*\n)/m
Regex: foo i början av raden, följt av valfritt antal icke-nylinjiga tecken, följt av en ny rad, följt omedelbart av ”stapel” och resten av raden med stapel i den./m
i slutet av regex betyder ”matchning över flera rader” -
print $1
Skriv ut delen av regex som var inom parentes (i det här fallet hela reguljäruttrycket) -
s/^(foo[^\n]*\nbar[^\n]*\n)//m
Radera den första matchningen för regex, så att vi kan matcha flera fall av regex i filen i fråga
Och utdata:
foo line 1 bar line 2 foo bar line 6
Kommentarer
- Kom bara in för att säga att din Perl kan förkortas till det mer idiomatiska:
perl -n0777E 'say $& while /^foo.*\nbar.*\n/mg' foo
Svar
Om vi vill få texten mellan de två mönstren exklusive sig själva.
Antag att vi har filen test.txt som innehåller:
blabla blabla foo here is the text to keep between the 2 patterns bar blabla blabla
Följande kod kan användas:
sed -n "/foo/{ n b gotoloop :loop N :gotoloop /bar/!{ h b loop } /bar/{ g p } }" test.txt
För följande utdata:
here is the text to keep between the 2 patterns
Hur fungerar det, låt ”s gör det steg för steg
-
/foo/{
utlöses när raden innehåller ”foo” -
n
byt ut mönsterutrymmet med nästa rad, dvs. ordet ”här” -
b gotoloop
gren till etiketten ”gotoloop” -
:gotoloop
definierar etiketten ”gotoloop” -
/bar/!{
om mönstret inte innehåller ”bar” -
h
ersätt hållutrymmet med mönster, så ”här” sparas i hållutrymmet -
b loop
gren till etiketten ”loop” -
:loop
definierar etiketten ”loop” -
N
lägger till mönstret i hållutrymmet.
Nu innehåller utrymmet:
”här”
”är” -
:gotoloop
Vi är nu i steg 4 och slingrar tills en rad innehåller ”bar” -
/bar/
loop är klar, ”bar” har hittats, det ” s mönsterutrymmet - mönsterutrymme ersätts med hållutrymme som innehåller alla raderna mellan” foo ”och” bar ”som har sparats under huvudslingan
-
p
kopiera mönsterutrymme till standardutdata
Klar!
Kommentarer
- Bra gjort, +1. Jag undviker vanligtvis att använda dessa kommandon genom att tr ’ ingöra de nya raderna i SOH och utföra normala sed-kommandon och sedan ersätta de nya raderna.
grep
. De är nära besläktade men inte dups, IMO."grep"
föreslår verbet ” till grep ”, och svar på topp, inklusive accepterat, använd inte ’ grep.