Zdá se, že zneužívám grep
/ egrep
.
Snažil jsem se hledat řetězce ve více řádcích a nemohl jsem najít shodu, zatímco vím, že to, co hledám, by se mělo shodovat. Původně jsem si myslel, že moje regulární výrazy jsou špatné, ale nakonec jsem si přečetl, že tyto nástroje fungují na řádek (také moje regulární výrazy byly tak triviální, že to nemohlo být problém).
Takže jaký nástroj by člověk použil k vyhledávání vzorů na více řádcích?
Komentáře
Odpověď
Zde „sa sed
ten, který vám poskytne grep
chování podobné napříč řádky:
sed -n "/foo/{:start /bar/!{N;b start};/your_regex/p}" your_file
Jak to funguje
-
-n
potlačuje výchozí chování tisku každého řádku -
/foo/{}
instruuje jej, aby odpovídalfoo
a udělejte, co přijde uvnitř klikyháků na odpovídající řádky. Nahraďtefoo
počáteční částí vzoru. -
:start
je větvící štítek, který nám pomáhá udržovat smyčky, dokud nenajdeme konec našeho regexu. -
/bar/!{}
provede to, co je v klikačkách řádky, které neodpovídajíbar
. Nahraďte s koncovou částí vzoru. -
N
připojí další řádek k aktivní vyrovnávací paměti (sed
tomu říká vzorový prostor) -
b start
se bezpodmínečně rozvětví nastart
štítek, který jsme vytvořili dříve, aby bylo možné přidávat další řádek, dokud vzorový prostor neobsahujebar
. -
/your_regex/p
vytiskne vzorový prostor, pokud odpovídáyour_regex
. Měli byste nahradityour_regex
celým výrazem, který chcete shodovat na více řádcích.
Komentáře
- +1 Přidání tohoto do nástroje! Děkujeme.
- Poznámka: V systému MacOS to dává
sed: 1: "/foo/{:start /bar/!{N;b ...": unexpected EOF (pending }'s)
- chyba
sed: unterminated {
chyba - @Nomaed Zde střílíš ve tmě, ale obsahuje tvůj regulární výraz náhodou nějaké “ {“ znaky? Pokud ano, ‚ budete muset zpětně lomítkem uniknout.
- @Nomaed Zdá se, že to má co do činění s rozdíly mezi
sed
implementacemi. Snažil jsem se dodržovat doporučení v této odpovědi, aby byl výše uvedený skript standardně kompatibilní, ale řekl mi, že “ start “ byl nedefinovaný označení. Takže si ‚ nejsem jistý, zda to lze provést standardním způsobem. Pokud to zvládnete, neváhejte upravit moji odpověď.
Odpověď
Obecně používám nástroj nazvaný pcregrep
který lze nainstalovat na většinu linuxových verzí pomocí yum
nebo apt
.
Např.
Předpokládejme, že pokud máte soubor s názvem testfile
s obsahem
abc blah blah blah def blah blah blah
Můžete spustit následující příkaz:
$ pcregrep -M "abc.*(\n|.)*def" testfile
provést porovnávání vzorů na více řádcích.
Navíc totéž můžete udělat s sed
.
$ sed -e "/abc/,/def/!d" testfile
Komentáře
- tento
sed
návrh přeskočí řádek, kde by se našeldef
odpověď
jednoduše tuto práci provede normální grep, který podporuje Perl-regexp
parametr P
.
$ echo "abc blah blah blah def blah blah blah" | grep -oPz "(?s)abc.*?def" abc blah blah blah def
(?s)
s názvem modifikátor DOTALL, který ve vašem regulárním výrazu vytvoří tečku tak, aby odpovídala nejen znakům, ale také zalomení řádků.
Komentáře
- Když zkusím toto řešení, výstup nekončí ‚ def ‚ ale jde na konec souboru ‚ blah ‚
- možná váš grep nepodporuje
-P
možnost - Toto bylo jediné, co pro mě fungovalo – vyzkoušel jsem všechny
sed
návrhy, ale ‚ nešel tak daleko, jak instalovat alternativy grep. -
$ grep --version
:grep (GNU grep) 3.1
v Windows Git Bash má možnost-P, --perl-regexp
, ale(?s)
není nefunguje. Stále zobrazuje pouze první řádek. Stejný vzor se stejným testovacím řetězcem funguje na regex101.com . Existuje alternativa v Git Bash?sed
? (sed (GNU sed) 4.8
zde) - Víte, jak přidat kontext do výstupu? grep -1 zde ‚ nefunguje.
Odpovědět
Zde je jednodušší přístup pomocí Perlu:
perl -e "$f=join("",<>); print $& if $f=~/foo\nbar.*\n/m" file
nebo (protože JosephR převzal sed
trasa , bez ostychu ukradnu jeho návrh )
perl -n000e "print $& while /^foo.*\nbar.*\n/mg" file
### Vysvětlení
$f=join("",<>);
: přečte celý soubor a uloží jeho obsah (nové řádky a vše) do proměnné $f
. Poté se pokusíme najít shodu foo\nbar.*\n
a vytisknout ji, pokud se shoduje (speciální proměnná $&
obsahuje poslední nalezenou shodu). ///m
je potřeba, aby se regulární výraz shodoval s novými řádky.
-0
nastavuje oddělovač vstupních záznamů. Nastavením na 00
se aktivuje „odstavec“, kde Perl použije jako oddělovač záznamu po sobě jdoucí nové řádky (\n\n
). V případech, kdy neexistují žádné po sobě jdoucí nové řádky, je celý soubor přečten (useknut) najednou.
### Upozornění: ne to nedělejte u velkých souborů, načte se celý soubor do paměti a to může být problém.
Komentáře
- Nemám ‚ t toho o Perlu víte hodně, ale ‚ t to musí být
my $f=join("",<>);
, přísně vzato? - pouze @Sapphire_Brick pokud jste v přísném režimu (
use strict;
). ‚ Je dobrým zvykem se do toho dostat, zvláště když píšete větší skripty, ale ‚ je to nadměrné pro takového malého linera jeden.
Odpověď
Předpokládejme, že máme soubor test.txt obsahující:
blabla blabla foo here is the text to keep between the 2 patterns bar blabla blabla
Lze použít následující kód:
sed -n "/foo/,/bar/p" test.txt
Následující výstup:
foo here is the text to keep between the 2 patterns bar
Odpovědět
Alternativa grep sift podporuje víceřádkové shody (odmítnutí odpovědnosti: jsem autor).
Předpokládejme testfile
obsahuje:
<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>'
(zobrazit řádky obsahující popis)
Výsledek:
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
(výpis a přeformátovat popis)
Výsledek:
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
Komentáře
- Velmi pěkný nástroj. Gratulujeme! Zkuste to zahrnout do distribucí, jako je Ubuntu.
Odpovědět
Vyřešil jsem to pomocí grep a – Možnost s jiným grepem.
grep first_line_word -A 1 testfile | grep second_line_word
Možnost -A 1 vytiskne za nalezeným řádkem 1 řádek. Samozřejmě to záleží na vašem souboru a kombinaci slov. Ale pro mě to bylo nejrychlejší a spolehlivé řešení.
Komentáře
- alias grepp = ‚ grep –color = auto -B10 -A20 -i ‚ pak cat somefile | grepp bla | grepp foo | grepp bar … ano ty -A a -B jsou velmi užitečné …máte nejlepší odpověď
- Toto není ‚ t super deterministické a ignoruje celý vzor ve prospěch získání jiného jediného řádku (pouze na základě jeho blízkosti do prvního řádku). ‚ Je lepší říci programu, aby šel tak daleko, že musí jít, aby se dostal k nějakému vzoru, který ‚ re absolutně jistý je konec textu, který ‚ zkoušíte najít. Například pokud se
testfile
aktualizuje tak, žesecond_line_word
je na třetím řádku, pak vám nejen chybí první řádek (kvůli váš druhýgrep
), ale ‚ vám nechybí řádek, který se mezi nimi začal zobrazovat. - Toto by bylo dost dobré MO pro příkazy ad hoc, kde opravdu chcete jen jeden řádek na výstupu, který jste již pochopili. Nemyslím si ‚, že ‚ je to, po čem OP je, a pravděpodobně byste v tom okamžiku mohli také kopírovat / vložit je to ad hoc.
Odpověď
Jedním ze způsobů, jak toho dosáhnout, je Perl. např. zde je obsah souboru s názvem foo
:
foo line 1 bar line 2 foo foo foo line 5 foo bar line 6
Tady je nějaký Perl, který shoda s jakýmkoli řádkem, který začíná na foo, následovaný jakýmkoli řádkem, který začíná barem:
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, rozdělené:
-
while(<>){$all .= $_}
Tím se načte celý standardní vstup do proměnné$all
-
while($all =~
Zatímco proměnnáall
má regulární výraz … -
/^(foo[^\n]*\nbar[^\n]*\n)/m
Regex: foo na začátku řádku, následovaný libovolným počtem znaků, které nejsou novým řádkem, následuje nový řádek, následovaný okamžitě „barem“ a zbytek řádku s barem v něm./m
na konci regexu znamená „shoda napříč více řádky“ -
print $1
Vytisknout část regexu to bylo v závorkách (v tomto případě celý regulární výraz) -
s/^(foo[^\n]*\nbar[^\n]*\n)//m
Vymažte první shodu pro regex, abychom mohli párovat více případů regexu v daném souboru
A výstup:
foo line 1 bar line 2 foo bar line 6
Komentáře
- Právě jste prohlásili, že vaše Perl lze zkrátit na více idiomatický:
perl -n0777E 'say $& while /^foo.*\nbar.*\n/mg' foo
Odpovědět
Pokud chceme dostat text mezi 2 vzory, které se samy vylučují.
Předpokládejme, že máme soubor test.txt obsahující:
blabla blabla foo here is the text to keep between the 2 patterns bar blabla blabla
Lze použít následující kód:
sed -n "/foo/{ n b gotoloop :loop N :gotoloop /bar/!{ h b loop } /bar/{ g p } }" test.txt
Následující výstup:
here is the text to keep between the 2 patterns
Jak to funguje, pojďme udělejte to krok za krokem
-
/foo/{
se spustí, když řádek obsahuje „foo“ -
n
nahraďte prostor vzoru dalším řádkem, tj. slovem „zde“ -
b gotoloop
větev štítku „gotoloop“ -
:gotoloop
definuje štítek „gotoloop“ -
/bar/!{
pokud vzor neobsahuje „lištu“ -
h
nahradit pozdržený prostor vzorem, takže „zde“ se uloží do pozdrženého prostoru -
b loop
větev k označení „smyčka“ -
:loop
definuje označení „smyčka“ -
N
připojí vzor k pozdrženému prostoru.
Nyní přidržený prostor obsahuje:
„here“
„je“ -
:gotoloop
Nyní jsme v kroku 4 a provádíme smyčku, dokud řádek neobsahuje „bar“ -
/bar/
smyčka není dokončena, „bar“ nebyl nalezen, to “ s prostor vzorů - vzorový prostor je nahrazen pozdrženým prostorem, který obsahuje všechny řádky mezi“ foo „a“ bar „, které byly uloženy během hlavní smyčky
-
p
zkopírovat vzorový prostor na standardní výstup
Hotovo!
Komentáře
- Dobrá práce, +1. Obvykle se vyhýbám použití těchto příkazů tr ‚ ing nových řádků do SOH a provádění normálních sed příkazů a poté nové řádky nahrazuji.
grep
. Jsou úzce spjaty, ale ne dupoty, IMO."grep"
navrhující sloveso “ pozdravit “ a hlavní odpovědi, včetně přijatých nepoužívejte ‚ t grep.