Як використовувати sed / grep для вилучення тексту між двома словами?


134

Я намагаюся вивести рядок, який містить все між двома словами рядка:

вхід:

"Here is a String"

вихід:

"is a"

Використання:

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

включає кінцеві точки, але я не хочу їх включати.


8
Яким повинен бути результат, якщо введення Here is a Here String? Або I Hereby Dub Thee Sir Stringy?
ghoti

5
FYI. Ваша команда означає надрукувати все між рядком, у якому є слово Here, і рядком, у якому є слово String - не те, що потрібно.
Хай Ву

Інший поширений sedFAQ - "як я можу витягувати текст між певними рядками"; це stackoverflow.com/questions/16643288 / ...
tripleee

Відповіді:


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

2
Дякую! Що робити, якщо я хотів знайти все між "один є" і "Рядок" у "Ось один - це струна"? (sed -e 's / one is (. *) String / \ 1 /'?
user1190650

5
@ user1190650 Це спрацювало б, якщо ви хочете побачити "Ось". Ви можете перевірити це: echo "Here is a one is a String" | sed -e 's/one is\(.*\)String/\1/'. Якщо ви просто хочете частина між «один» і «String», то вам потрібно зробити регулярний вираз відповідає всій лінії: sed -e 's/.*one is\(.*\)String.*/\1/'. У седі s/pattern/replacement/скажіть "замінити" заміну "на" шаблон "у кожному рядку". Він змінить лише все, що відповідає "шаблону", тому якщо ви хочете, щоб він замінив цілу лінію, вам потрібно зробити "шаблон" відповідним цілому рядку.
Брайан Кемпбелл

9
Це перерва, коли вхідHere is a String Here is a String
Jay D

1
Було б чудово побачити рішення для випадку: "Ось бла-бла-струна. Ось 1 бла-бла-струна. Ось 2-й рядок" блаш-блаш ". Вихід повинен підбирати лише першу підрядку між Here та String
Jay D

1
@JayD sed не підтримує не жадібну відповідність, див. Це питання щодо деяких рекомендованих альтернатив.
Брайан Кемпбелл

180

GNU grep також може підтримувати позитивний та негативний погляд вперед та огляд назад: Для вашого випадку командою було б:

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

За наявності кількох входжень Hereі string, ви можете вибрати , чи хочете ви , щоб відповідати від першого Hereі останнього stringабо зіставити їх по окремості. З точки зору регулярного вираження, його називають жадібним матчем (перший випадок) або нежадливим збігом (другий випадок)

$ 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 

31
Зауважте, що -Pопція GNU grep не існує у grepвключеному до * BSD або в тих, що постачаються з будь-яким SVR4 (Solaris тощо). У FreeBSD ви можете встановити devel/pcreпорт, який включає pcregrep, який підтримує PCRE (і вперед / назад). Старіші версії OSX, які використовуються GNU grep, але в OSX Mavericks, -Pпоходить від версії FreeBSD, яка не включає опцію.
ghoti

1
Привіт, як я витягую лише окремий вміст?
Дургеш Сутар

4
Це не працює, тому що якщо ваш кінцевий рядок "string" трапляється більше одного разу, він отримає останнє , а не наступне .
Buttle Butkus

6
У разі Here is a string a string, як " is a " і " is a string a "є дійсними відповіді (ігнорувати лапки), відповідно з вимогами питання. Від вас залежить, який з них ви хочете, і тоді відповідь може бути відповідно різною. У будь-якому випадку, для вашої вимоги це спрацює:echo "Here is a string a string" | grep -o -P '(?<=Here).*?(?=string)'
anishsane

2
@BND, вам потрібно включити функцію багаторядкового пошуку pcregrep . echo $'Here is \na string' | grep -zoP '(?<=Here)(?s).*(?=string)'
anishsane

58

Прийнята відповідь не видаляє текст, який міг бути до Hereабо після String. Це буде:

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

Основна відмінність - додавання .*безпосередньо до Hereі після String.


Ваша відповідь є багатообіцяючою. Хоча одне питання. Як я можу витягнути його до першої поміченої рядка, якщо в одному рядку є кілька рядків? Спасибі
Міан Асбат Ахмад

@MianAsbatAhmad Ви хочете зробити *кількісний коефіцієнт між Hereі Stringне-жадібним (або ледачим). Однак тип регулярного вираження, використовуваний sed, не підтримує ліниві квантори ( ?одразу ж після .*) відповідно до цього питання Стакковержа. Зазвичай для реалізації ледачого квантора ви б просто відповідати проти всього , що не є маркером , ви не хочете , щоб відповідати, але в цьому випадку є не тільки один маркер, а його цілий рядок String.
Уїлер

Спасибі, я отримав відповідь , використовуючи AWK, stackoverflow.com/questions/51041463 / ...
Міан Асбат Ahmad

На жаль, це не працює, якщо в рядку є розриви рядків
Witalo Benicio

Це не повинно. .не відповідає розривам рядків Якщо ви хочете відповідати розривам рядків, ви можете замінити .чимось на кшталт [\s\s].
Уїлер

35

Ви можете знімати рядки лише в Bash :

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

І якщо у вас є GNU grep, що включає PCRE , ви можете використовувати твердження нульової ширини:

$ echo "Here is a String" | grep -Po '(?<=(Here )).*(?= String)'
is a

чому цей метод настільки повільний? при зачистці великої сторінки HTML за допомогою цього методу потрібно приблизно 10 секунд.
Адам Джонс

@AdamJohns, який метод? PCRE один? PCRE досить складний для розбору, але 10 секунд здається екстремальним. Якщо вас хвилює, я рекомендую вам поставити запитання, включаючи приклад коду, і подивитися, що кажуть експерти.
ghoti

Я думаю, що це було так повільно для мене, тому що він містив дуже велике джерело html-файлу в змінній. Коли я писав вміст у файл, а потім розбирав файл, швидкість різко зросла.
Адам Джонс

22

Через GNU awk,

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

grep з -P( perl-regexp ) підтримує параметр \K, який допомагає відкинути раніше відповідні символи. У нашому випадку раніше узгоджений рядок був Hereтаким, що його відкидали від кінцевого виводу.

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

Якщо ви хочете, щоб результат is aбув, ви можете спробувати нижче,

$ 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

Це не працює для: echo "Here is a string dfdsf Here is a string" | awk -v FS="(Here|string)" '{print $2}'він повертається лише is aзамість повинен бути is a is a@Avinash Raj
alper

20

Якщо у вас довгий файл з безліччю багаторядкових точок, корисно спочатку надрукувати рядки з цифрами:

cat -n file | sed -n '/Here/,/String/p'

3
Дякую! Це єдине рішення, яке працювало в моєму випадку (текстовий файл з декількома рядками, а не один рядок без розривів рядків). Очевидно, щоб мати його без нумерації рядків, -nпараметр в catповинен бути пропущений.
Джеффрі Лебовський

... у цьому випадку catможна повністю пропустити; sedвміє читати файл або стандартний вхід.
tripleee

9

Це може допомогти вам (GNU sed):

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

Це представляє кожне подання тексту між двома маркерами (у цьому випадку Hereта String) на новому рядку та зберігає нові рядки в тексті.


7

Усі вищезазначені рішення мають недоліки, коли останній рядок пошуку повторюється в іншому місці рядка. Мені було найкраще написати функцію 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"

6

Можна використовувати дві команди s

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

Також працює

$ 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 

6

Щоб зрозуміти sedкоманду, ми повинні будувати її поетапно.

Ось ваш оригінальний текст

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

Спробуємо видалити Hereрядок з sопцією ubstition вsed

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

На даний момент, я вважаю , ви могли б видалити String, а

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

Але це не ваш бажаний результат.

Для комбінування двох команд sed використовуйте -eпараметр

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

Сподіваюся, це допомагає


4

Ви можете використовувати \1(див. Http://www.grymoire.com/Unix/Sed.html#uh-4 ):

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

Вміст, що знаходиться всередині дужок, буде зберігатися як \1.


Це видаляє рядки, а не виводить щось середнє. Спробуйте видалити "Hello" з "is" у команді sed, і вона виведе "Hello a"
Джонатан

1

Проблема. Мої збережені повідомлення Claws Mail загортаються так, і я намагаюся витягнути рядки Subject:

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>

Як A2 у цій темі, Як використовувати sed / grep для вилучення тексту між двома словами? перший вираз внизу "працює" до тих пір, поки відповідний текст не містить нового рядка:

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

[SLC38A9 lysosomal arginine sensor; mTORC1 pathway] Key

Однак, незважаючи на спробу численних варіантів ( .+?; /s; ...), я не зміг змусити їх працювати:

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

Рішення 1.

Текст на вилучення між двома рядками в різних рядках

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

що дає

[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]]                              

Рішення 2. *

Per Як я можу замінити символ нового рядка (\ п) з використанням СЕД?

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

замінить нові рядки пробілом.

Зв'язавши це з A2 в Як використовувати sed / grep для вилучення тексту між двома словами? , ми отримуємо:

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

що дає

[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]] 

Цей варіант видаляє подвійні пробіли:

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

давання

[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]]

1
приємна пригода :))
Олександру-Міхай Манолеску
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.