Jak używać sed / grep do wyodrębniania tekstu między dwoma słowami?

134

Próbuję wyprowadzić ciąg, który zawiera wszystko między dwoma słowami ciągu:

Wejście:

"Here is a String"

wynik:

"is a"

Za pomocą:

sed -n '/Here/,/String/p'

zawiera punkty końcowe, ale nie chcę ich uwzględniać.

user1190650
źródło
8
Jaki powinien być wynik, jeśli dane wejściowe to Here is a Here String? Albo I Hereby Dub Thee Sir Stringy?
ghoti
5
FYI. Twoje polecenie oznacza wydrukowanie wszystkiego między wierszem zawierającym słowo Here a wierszem zawierającym słowo String - nie to, co chcesz.
Hai Vu
Inne często sedzadawane pytania to „jak wyodrębnić tekst między poszczególnymi wierszami”; to jest stackoverflow.com/questions/16643288/ ...
tripleee

Odpowiedzi:

109
sed -e 's/Here\(.*\)String/\1/'
Brian Campbell
źródło
2
Dzięki! A co jeśli chciałbym znaleźć wszystko pomiędzy „jeden to” a „ciągiem” w „tu jest jeden to ciąg”? (sed -e 's / one to (. *) String / \ 1 /'?
user1190650
5
@ user1190650 To zadziała, jeśli chcesz również zobaczyć „Tutaj jest”. Można przetestować go: echo "Here is a one is a String" | sed -e 's/one is\(.*\)String/\1/'. Jeśli chcesz tylko część między „jest” i „String”, to trzeba zrobić regex dopasować cały wiersz: sed -e 's/.*one is\(.*\)String.*/\1/'. W sed, s/pattern/replacement/powiedz „zastąp 'zamiennik' dla 'wzorca' w każdym wierszu”. Zmieni tylko wszystko, co pasuje do „wzorca”, więc jeśli chcesz zastąpić całą linię, musisz dopasować „wzorzec” do całej linii.
Brian Campbell
9
To się psuje, gdy wejście toHere is a String Here is a String
Jay D
1
Byłoby wspaniale zobaczyć rozwiązanie dla przypadku: „Oto bla bla Ciąg Oto 1 a bla bla Ciąg Tutaj jest 2 blash blash Ciąg” wyjście powinno pobrać tylko pierwszy podciąg między Tutaj a Ciągiem ”
Jay D
1
@JayD sed nie obsługuje niechcianych dopasowań, zobacz to pytanie, aby poznać zalecane alternatywy.
Brian Campbell
180

GNU grep może również obsługiwać pozytywne i negatywne przewidywanie i cofanie: w twoim przypadku polecenie wyglądałoby tak:

echo "Here is a string" | grep -o -P '(?<=Here).*(?=string)'

Jeśli występuje wiele wystąpień Herei string, możesz wybrać, czy chcesz dopasować od pierwszego Heredo ostatniego, stringczy też dopasować je indywidualnie. W przypadku wyrażenia regularnego nazywa się to dopasowaniem zachłannym (pierwszy przypadek) lub niechcianym dopasowaniem (drugi przypadek)

$ echo 'Here is a string, and Here is another string.' | grep -oP '(?<=Here).*(?=string)' # Greedy match
 is a string, and Here is another 
$ echo 'Here is a string, and Here is another string.' | grep -oP '(?<=Here).*?(?=string)' # Non-greedy match (Notice the '?' after '*' in .*)
 is a 
 is another 
anishsane
źródło
31
Zauważ, że -Popcja GNU grep nie istnieje w grepdołączonym do * BSD, ani w tych, które są dostarczane z jakimkolwiek SVR4 (Solaris itp.). We FreeBSD można zainstalować devel/pcreport, który zawiera pcregrep, który obsługuje PCRE (i antycypację / wstecz). Starsze wersje OSX wykorzystywały GNU grep, ale w OSX Mavericks -Pwywodzi się z wersji FreeBSD, która nie zawiera tej opcji.
ghoti
1
Cześć, jak wyodrębnić tylko odrębną zawartość?
Durgesh Suthar,
4
To nie działa, ponieważ jeśli końcowy ciąg znaków „łańcuch” występuje więcej niż raz, otrzyma ostatnie wystąpienie, a nie następne wystąpienie.
Buttle Butkus
6
W przypadku Here is a string a string, oba " is a " i " is a string a "są poprawnymi odpowiedziami (zignoruj ​​cudzysłowy), zgodnie z wymaganiami pytania. To zależy od Ciebie, które jeden z nich ty chcesz, a następnie odpowiedź może być inna odpowiednio. W każdym razie, według twojego wymagania, to zadziała:echo "Here is a string a string" | grep -o -P '(?<=Here).*?(?=string)'
anishsane
2
@BND, musisz włączyć funkcję wyszukiwania wielowierszowego w pcregrep . echo $'Here is \na string' | grep -zoP '(?<=Here)(?s).*(?=string)'
anishsane
58

Zaakceptowana odpowiedź nie usuwa tekstu, który może znajdować się przed Herelub po String. To będzie:

sed -e 's/.*Here\(.*\)String.*/\1/'

Główną różnicą jest dodanie .*bezpośrednio przed Herei po String.

kołodziej
źródło
Twoja odpowiedź jest obiecująca. Jednak jeden problem. Jak mogę wyodrębnić go do pierwszego widocznego ciągu, jeśli w tym samym wierszu znajduje się wiele ciągów? Dzięki
Mian Asbat Ahmad
@MianAsbatAhmad Chciałbyś, aby *kwantyfikator między Herei był niechciwy String(lub leniwy). Jednak typ wyrażenia regularnego używany przez sed nie obsługuje leniwych kwantyfikatorów (a ?bezpośrednio po .*) zgodnie z tym pytaniem Stackoverflow. Zwykle, aby zaimplementować leniwy kwantyfikator, po prostu dopasujesz wszystko z wyjątkiem tokena, którego nie chcesz dopasować, ale w tym przypadku nie ma tylko jednego tokenu, zamiast tego jest to cały ciąg String.
Wheeler
Dzięki, mam odpowiedź za pomocą awk, stackoverflow.com/questions/51041463/...
Mian asbat Ahmad
Niestety to nie działa, jeśli ciąg ma
znaki końca
Nie powinno. .nie odpowiada podziałom linii. Jeśli chcesz dopasować podziały linii, możesz zastąpić .coś w rodzaju [\s\s].
Wheeler
35

Możesz usunąć struny w samym Bash :

$ foo="Here is a String"
$ foo=${foo##*Here }
$ echo "$foo"
is a String
$ foo=${foo%% String*}
$ echo "$foo"
is a
$

A jeśli masz GNU grep, które zawiera PCRE , możesz użyć asercji o zerowej szerokości:

$ echo "Here is a String" | grep -Po '(?<=(Here )).*(?= String)'
is a
ghoti
źródło
dlaczego ta metoda jest tak powolna? podczas usuwania dużej strony html tą metodą trwa to około 10 sekund.
Adam Johns
@AdamJohns, która metoda? PCRE jeden? Analiza PCRE jest dość skomplikowana, ale 10 sekund wydaje się ekstremalne. Jeśli jesteś zaniepokojony, radzę zadać pytanie z przykładowym kodem i zobaczyć, co mówią eksperci.
ghoti
Myślę, że było to dla mnie tak powolne, ponieważ zawierało bardzo duże źródło pliku html w zmiennej. Kiedy zapisałem zawartość do pliku, a następnie przeanalizowałem plik, prędkość dramatycznie wzrosła.
Adam Johns
22

Dzięki GNU awk

$ echo "Here is a string" | awk -v FS="(Here|string)" '{print $2}'
 is a 

grep z obsługą parametrów -P( perl-regexp ) \K, co pomaga w odrzucaniu wcześniej dopasowanych znaków. W naszym przypadku poprzednio dopasowanym ciągiem byłHere usunięty z końcowego wyniku.

$ echo "Here is a string" | grep -oP 'Here\K.*(?=string)'
 is a 
$ echo "Here is a string" | grep -oP 'Here\K(?:(?!string).)*'
 is a 

Jeśli chcesz, aby wynik był, is amożesz wypróbować poniższe,

$ echo "Here is a string" | grep -oP 'Here\s*\K.*(?=\s+string)'
is a
$ echo "Here is a string" | grep -oP 'Here\s*\K(?:(?!\s+string).)*'
is a
Avinash Raj
źródło
To nie działa dla :, echo "Here is a string dfdsf Here is a string" | awk -v FS="(Here|string)" '{print $2}'zwraca tylko is azamiast powinno być is a is a@Avinash Raj
alper
20

Jeśli masz długi plik z wieloma wystąpieniami w wielu wierszach, warto najpierw wydrukować wiersze liczbowe:

cat -n file | sed -n '/Here/,/String/p'
alemol
źródło
3
Dzięki! To jedyne rozwiązanie, które zadziałało w moim przypadku (plik tekstowy z wieloma wierszami, a nie pojedynczy ciąg bez znaków końca wiersza). Oczywiście, aby mieć to bez numeracji linii, należy pominąć -nopcję in cat.
Jeffrey Lebowski
... w takim przypadku catmożna całkowicie pominąć; sedwie, jak czytać plik lub standardowe wejście.
tripleee
9

To może zadziałać dla Ciebie (GNU sed):

sed '/Here/!d;s//&\n/;s/.*\n//;:a;/String/bb;$!{n;ba};:b;s//\n&/;P;D' file 

Przedstawia każdą reprezentację tekstu między dwoma znacznikami (w tym przypadku Herei String) w nowej linii i zachowuje nowe linie w tekście.

potong
źródło
7

Wszystkie powyższe rozwiązania mają wady, w których ostatni ciąg wyszukiwania jest powtarzany w innym miejscu ciągu. Uważam, że najlepiej jest napisać funkcję bash.

    function str_str {
      local str
      str="${1#*${2}}"
      str="${str%%$3*}"
      echo -n "$str"
    }

    # test it ...
    mystr="this is a string"
    str_str "$mystr" "this " " string"
Gary Dean
źródło
6

Możesz użyć dwóch poleceń s

$ echo "Here is a String" | sed 's/.*Here//; s/String.*//'
 is a 

Działa również

$ echo "Here is a StringHere is a String" | sed 's/.*Here//; s/String.*//'
 is a

$ echo "Here is a StringHere is a StringHere is a StringHere is a String" | sed 's/.*Here//; s/String.*//'
 is a 
Ivan
źródło
6

Aby zrozumieć sedpolecenie, musimy je budować krok po kroku.

Oto Twój oryginalny tekst

user@linux:~$ echo "Here is a String"
Here is a String
user@linux:~$ 

Spróbujmy usunąć Herestring z sopcją ubstition wsed

user@linux:~$ echo "Here is a String" | sed 's/Here //'
is a String
user@linux:~$ 

W tym momencie, wierzę, będzie można usunąć Stringrównież

user@linux:~$ echo "Here is a String" | sed 's/String//'
Here is a
user@linux:~$ 

Ale to nie jest twój pożądany wynik.

Aby połączyć dwa polecenia seda, użyj -eopcji

user@linux:~$ echo "Here is a String" | sed -e 's/Here //' -e 's/String//'
is a
user@linux:~$ 

Mam nadzieję że to pomoże

Sabrina
źródło
4

Możesz użyć \1(patrz http://www.grymoire.com/Unix/Sed.html#uh-4 ):

echo "Hello is a String" | sed 's/Hello\(.*\)String/\1/g'

Treść znajdująca się w nawiasach zostanie zapisana jako \1.

mvairavan
źródło
To usuwa ciągi zamiast wyprowadzać coś pomiędzy. Spróbuj usunąć „Hello” z „is” w poleceniu sed, a wyświetli się „Hello a”
Jonathan
1

Problem. Moje zapisane wiadomości Claws Mail są opakowane w następujący sposób, a ja próbuję wyodrębnić wiersze tematu:

Subject: [SLC38A9 lysosomal arginine sensor; mTORC1 pathway] Key molecular
 link in major cell growth pathway: Findings point to new potential
 therapeutic target in pancreatic cancer [mTORC1 Activator SLC38A9 Is
 Required to Efflux Essential Amino Acids from Lysosomes and Use Protein as
 a Nutrient] [Re: Nutrient sensor in key growth-regulating metabolic pathway
 identified [Lysosomal amino acid transporter SLC38A9 signals arginine
 sufficiency to mTORC1]]
Message-ID: <20171019190902.18741771@VictoriasJourney.com>

Według A2 w tym wątku, jak używać sed / grep do wyodrębniania tekstu między dwoma słowami? pierwsze wyrażenie poniżej „działa”, o ile dopasowany tekst nie zawiera nowej linii:

grep -o -P '(?<=Subject: ).*(?=molecular)' corpus/01

[SLC38A9 lysosomal arginine sensor; mTORC1 pathway] Key

Jednak pomimo wypróbowania wielu wariantów ( .+?; /s; ...), nie mogłem sprawić, aby te działały:

grep -o -P '(?<=Subject: ).*(?=link)' corpus/01
grep -o -P '(?<=Subject: ).*(?=therapeutic)' corpus/01
etc.

Rozwiązanie 1.

Za tekst Wyciąg pomiędzy dwoma łańcuchami na różnych liniach

sed -n '/Subject: /{:a;N;/Message-ID:/!ba; s/\n/ /g; s/\s\s*/ /g; s/.*Subject: \|Message-ID:.*//g;p}' corpus/01

co daje

[SLC38A9 lysosomal arginine sensor; mTORC1 pathway] Key molecular link in major cell growth pathway: Findings point to new potential therapeutic target in pancreatic cancer [mTORC1 Activator SLC38A9 Is Required to Efflux Essential Amino Acids from Lysosomes and Use Protein as a Nutrient] [Re: Nutrient sensor in key growth-regulating metabolic pathway identified [Lysosomal amino acid transporter SLC38A9 signals arginine sufficiency to mTORC1]]                              

Rozwiązanie 2. *

Per Jak mogę zamienić znak nowej linii (\ n) używając seda?

sed ':a;N;$!ba;s/\n/ /g' corpus/01

zastąpi znaki nowej linii spacją.

Łączenie tego z A2 w Jak używać sed / grep do wyodrębniania tekstu między dwoma słowami? otrzymujemy:

sed ':a;N;$!ba;s/\n/ /g' corpus/01 | grep -o -P '(?<=Subject: ).*(?=Message-ID:)'

co daje

[SLC38A9 lysosomal arginine sensor; mTORC1 pathway] Key molecular  link in major cell growth pathway: Findings point to new potential  therapeutic target in pancreatic cancer [mTORC1 Activator SLC38A9 Is  Required to Efflux Essential Amino Acids from Lysosomes and Use Protein as  a Nutrient] [Re: Nutrient sensor in key growth-regulating metabolic pathway  identified [Lysosomal amino acid transporter SLC38A9 signals arginine  sufficiency to mTORC1]] 

Ten wariant usuwa podwójne spacje:

sed ':a;N;$!ba;s/\n/ /g; s/\s\s*/ /g' corpus/01 | grep -o -P '(?<=Subject: ).*(?=Message-ID:)'

dający

[SLC38A9 lysosomal arginine sensor; mTORC1 pathway] Key molecular link in major cell growth pathway: Findings point to new potential therapeutic target in pancreatic cancer [mTORC1 Activator SLC38A9 Is Required to Efflux Essential Amino Acids from Lysosomes and Use Protein as a Nutrient] [Re: Nutrient sensor in key growth-regulating metabolic pathway identified [Lysosomal amino acid transporter SLC38A9 signals arginine sufficiency to mTORC1]]
Victoria Stuart
źródło
1
niezła przygoda :))
Alexandru-Mihai Manolescu