Het lijkt erop dat ik grep
/ egrep
misbruik.
Ik probeerde te zoeken naar tekenreeksen in meerdere regels en kon geen overeenkomst vinden, terwijl ik weet dat wat ik zoek, overeen moet komen. Oorspronkelijk dacht ik dat mijn regexes verkeerd waren, maar ik las uiteindelijk dat deze tools werken per regel (ook mijn regexes waren zo triviaal dat het niet het probleem kon zijn).
Dus welke tool zou je gebruiken om patronen over meerdere regels te zoeken?
Opmerkingen
Antwoord
Hier “sa sed
een die je grep
-achtig gedrag op meerdere regels geeft:
sed -n "/foo/{:start /bar/!{N;b start};/your_regex/p}" your_file
Hoe het werkt
-
-n
onderdrukt het standaardgedrag van het afdrukken van elke regel -
/foo/{}
geeft aan dat het overeenkomt metfoo
en doe wat binnen de squigglies komt naar de overeenkomende regels. Vervangfoo
door het begingedeelte van het patroon. -
:start
is een vertakkingslabel om ons te helpen door te gaan totdat we het einde van onze regex hebben gevonden. -
/bar/!{}
zal uitvoeren wat in de squigglies staat de regels die niet “overeenkomen metbar
. Vervang met het eindgedeelte van het patroon. -
N
voegt de volgende regel toe aan de actieve buffer (sed
noemt dit de patroonruimte) -
b start
zal onvoorwaardelijk vertakken naar hetstart
label dat we hebben gemaakt eerder om de volgende regel te blijven toevoegen zolang de patroonruimte geenbar
bevat. -
/your_regex/p
drukt de patroonruimte af als deze overeenkomt metyour_regex
. U moetyour_regex
vervangen door de hele uitdrukking die u over meerdere regels wilt matchen.
Opmerkingen
- +1 Toevoegen aan de toolikt! Bedankt.
- Opmerking: op MacOS geeft dit
sed: 1: "/foo/{:start /bar/!{N;b ...": unexpected EOF (pending }'s)
-
sed: unterminated {
foutmelding - @Nomaed Hier in het donker opgenomen, maar bevat je regex toevallig ” {” -tekens? Als dit het geval is, moet u ‘ ze backslashen en escapen.
- @Nomaed Het lijkt erop dat het te maken heeft met de verschillen tussen
sed
implementaties. Ik heb geprobeerd de aanbevelingen in dat antwoord op te volgen om het bovenstaande script standaardcompatibel te maken, maar het vertelde me dat ” start ” een ongedefinieerde label. Dus ik ‘ m niet zeker of dit op een standaard-compatibele manier kan worden gedaan. Als het je lukt, aarzel dan niet om mijn antwoord te bewerken.
Antwoord
Ik gebruik meestal een tool genaamd pcregrep
die kan worden geïnstalleerd in de meeste Linux-versies met yum
of apt
.
Voor bijv.
Stel dat u een bestand heeft met de naam testfile
met inhoud
abc blah blah blah def blah blah blah
U kunt het volgende commando uitvoeren:
$ pcregrep -M "abc.*(\n|.)*def" testfile
om patroonovereenkomsten op meerdere regels uit te voeren.
Bovendien, u kunt hetzelfde doen met sed
.
$ sed -e "/abc/,/def/!d" testfile
Reacties
- deze
sed
suggestie wordt overgeslagen de regel waardef
zou worden gevonden
Antwoord
Gewoon een normale grep die Perl-regexp
parameter P
ondersteunt, zal dit werk doen.
$ echo "abc blah blah blah def blah blah blah" | grep -oPz "(?s)abc.*?def" abc blah blah blah def
(?s)
genaamd DOTALL modifier die ervoor zorgt dat de punt in je reguliere expressie niet alleen overeenkomt met de karakters maar ook met de regeleindes.
Reacties
- Wanneer ik deze oplossing probeer, eindigt de uitvoer niet op ‘ def ‘ maar gaat naar het einde van het bestand ‘ blah ‘
- misschien je grep ondersteunt geen
-P
optie - Dit was de enige die voor mij werkte – heb alle
sed
suggesties geprobeerd, maar ‘ ging niet zo ver als het installeren van grep-alternatieven. -
$ grep --version
:grep (GNU grep) 3.1
in de Windows Git Bash heeft een optie-P, --perl-regexp
maar(?s)
heeft geen ‘ t lijkt daar te werken. Het toont nog steeds alleen de eerste regel. Hetzelfde patroon met dezelfde testreeks werkt op regex101.com . Is er een alternatief in de Git Bash?sed
? (sed (GNU sed) 4.8
hier) - Weet jij hoe je context aan de uitvoer moet toevoegen? grep -1 werkt hier niet ‘ t.
Antwoord
Hier “een eenvoudigere benadering met Perl:
perl -e "$f=join("",<>); print $& if $f=~/foo\nbar.*\n/m" file
of (aangezien JosephR de sed
route , ik “zal schaamteloos zijn suggestie ) stelen
perl -n000e "print $& while /^foo.*\nbar.*\n/mg" file
### Uitleg
$f=join("",<>);
: dit leest het volledige bestand en slaat de inhoud (nieuwe regels en alles) op in de variabele $f
. We proberen vervolgens foo\nbar.*\n
te matchen, en het af te drukken als het overeenkomt (de speciale variabele $&
bevat de laatst gevonden overeenkomst). De ///m
is nodig om de reguliere expressie op nieuwe regels te laten matchen.
De -0
stelt het scheidingsteken voor invoerrecords in. Als u dit instelt op 00
, wordt de “alinea-modus” geactiveerd, waarbij Perl opeenvolgende nieuwe regels (\n\n
) als recordscheidingsteken gebruikt. In gevallen waarin er geen opeenvolgende nieuwe regels zijn, wordt het volledige bestand in één keer gelezen (geslurpt).
### Waarschuwing: doe dit niet voor grote bestanden, het wordt geladen het hele bestand in het geheugen en dat kan een probleem zijn.
Opmerkingen
- Ik don ‘ t weet veel over Perl, maar zou ‘ t het
my $f=join("",<>);
strikt genomen moeten zijn? - Alleen @Sapphire_Brick als je in strikte modus bent (
use strict;
). Het ‘ is een goede gewoonte om erin te komen, vooral bij het schrijven van grotere scripts, maar het ‘ is een overbodige luxe voor een kleine oneliner als deze één.
Antwoord
Stel dat we het bestand hebben test.txt met daarin:
blabla blabla foo here is the text to keep between the 2 patterns bar blabla blabla
De volgende code kan worden gebruikt:
sed -n "/foo/,/bar/p" test.txt
Voor de volgende uitvoer:
foo here is the text to keep between the 2 patterns bar
Antwoord
Het grep-alternatief sift ondersteunt overeenkomsten tussen meerdere regels (disclaimer: ik ben de auteur).
Stel dat testfile
bevat:
<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>'
(toon de regels met de beschrijving)
Resultaat:
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
(extract en formatteer de beschrijving opnieuw)
Resultaat:
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
Reacties
- Zeer mooie tool. Gefeliciteerd! Probeer het op te nemen in distributies zoals Ubuntu.
Answer
Ik heb deze voor mij opgelost met grep en – Een optie met een andere grep.
grep first_line_word -A 1 testfile | grep second_line_word
De -A 1 optie drukt 1 regel na de gevonden regel af. Het hangt natuurlijk af van uw bestand en woordcombinatie. Maar voor mij was het de snelste en betrouwbare oplossing.
Reacties
- alias grepp = ‘ grep –color = auto -B10 -A20 -i ‘ en dan kat een bestand | grepp blah | grepp foo | grepp bar … ja die -A en -B zijn erg handig …je hebt het beste antwoord
- Dit is niet ‘ t super deterministisch en het negeert het hele patroon om gewoon een andere enkele regel te krijgen (alleen gebaseerd op de nabijheid naar de eerste regel). Het ‘ is beter om het programma te vertellen hoe ver het ook moet gaan om tot een bepaald patroon te komen dat u ‘ absoluut zeker is het einde van de tekst die u ‘ probeert te matchen. Als bijvoorbeeld
testfile
zo wordt bijgewerkt datsecond_line_word
op de derde regel staat, dan mist u niet alleen de eerste regel (vanwege je tweedegrep
) maar je ‘ mist de regel die tussen de twee begon te verschijnen niet. - Dit zou een goed genoeg MO zijn voor ad-hocopdrachten waarbij je eigenlijk maar een enkele regel in de uitvoer wilt die je al begrijpt. Ik denk niet ‘ niet dat ‘ s is waar het OP naar op zoek is en je zou waarschijnlijk ook gewoon kunnen kopiëren / plakken op dat punt vanwege het is ad hoc.
Answer
Een manier om dit te doen is met Perl. bijv. hier is de inhoud van een bestand met de naam foo
:
foo line 1 bar line 2 foo foo foo line 5 foo bar line 6
Nu, hier is wat Perl die match met elke regel die begint met foo gevolgd door elke regel die begint met 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; }"
De Perl, uitgesplitst:
-
while(<>){$all .= $_}
Dit laadt de volledige standaardinvoer in de variabele$all
-
while($all =~
Terwijl de variabeleall
de reguliere expressie heeft … -
/^(foo[^\n]*\nbar[^\n]*\n)/m
De regex: foo aan het begin van de regel, gevolgd door een willekeurig aantal niet-nieuwe regel tekens, gevolgd door een nieuwe regel, onmiddellijk gevolgd door “bar”, en de rest van de regel met bar erin./m
aan het einde van de regex betekent “match over meerdere regels” -
print $1
Druk het deel van de regex af dat tussen haakjes stond (in dit geval de volledige reguliere expressie) -
s/^(foo[^\n]*\nbar[^\n]*\n)//m
Wis de eerste overeenkomst voor de regex, zodat we meerdere gevallen van de regex kunnen matchen in het betreffende bestand
En de output:
foo line 1 bar line 2 foo bar line 6
Opmerkingen
- Kom langs om te zeggen dat uw Perl kan worden ingekort tot het meer idiomatische:
perl -n0777E 'say $& while /^foo.*\nbar.*\n/mg' foo
Antwoord
Als we de tekst tussen de 2 patronen willen krijgen, met uitzondering van zichzelf.
Stel dat we het bestand test.txt met daarin:
blabla blabla foo here is the text to keep between the 2 patterns bar blabla blabla
De volgende code kan worden gebruikt:
sed -n "/foo/{ n b gotoloop :loop N :gotoloop /bar/!{ h b loop } /bar/{ g p } }" test.txt
Voor de volgende uitvoer:
here is the text to keep between the 2 patterns
Hoe werkt het, laten we maak het stap voor stap
-
/foo/{
wordt geactiveerd wanneer regel “foo” bevat -
n
vervang de patroonruimte door de volgende regel, dwz het woord “hier” -
b gotoloop
vertakking naar het label “gotoloop” -
:gotoloop
definieert het label “gotoloop” -
/bar/!{
als het patroon geen “bar” bevat -
h
vervang de hold-spatie door patroon, dus “here” wordt opgeslagen in de hold-ruimte -
b loop
vertakking naar het label “loop” -
:loop
definieert het label “loop” -
N
voegt het patroon toe aan de hold-ruimte.
De hold-ruimte bevat nu:
“hier”
“is de” -
:gotoloop
We zijn nu bij stap 4, en herhalen totdat een regel “bar” bevat -
/bar/
lus is voltooid, “bar” is gevonden, it ” s de patroonruimte - patroonruimte is vervangen door hold-spatie die alle regels tussen” foo “en” bar “bevat die zijn opgeslagen tijdens de hoofdlus
-
p
kopieer patroonruimte naar standaarduitvoer
Klaar!
Reacties
- Goed gedaan, +1. Ik gebruik deze commandos meestal niet door tr ‘ de nieuwe regels in SOH te zetten en normale sed-opdrachten uit te voeren en de nieuwe regels te vervangen.
grep
. Ze zijn nauw verwant, maar geen dups, IMO."grep"
en suggereerde het werkwoord ” naar grep “, en de beste antwoorden, inclusief geaccepteerde, gebruik ‘ t grep.