Как использовать 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, а не тем, что вы хотите.
Хай Ву

Другой часто sedзадаваемый вопрос - «как выделить текст между отдельными строками»; это stackoverflow.com/questions/16643288/…
tripleee

Ответы:


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

2
Спасибо! Что, если бы я хотел найти все между «one is» и «String» в «Here is a one is a String»? (sed -e 's / one is (. *) String / \ 1 /'?
user1190650 06

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/'. В sed s/pattern/replacement/скажите «заменить« замену »на« шаблон »в каждой строке». Он изменит только то, что соответствует «шаблону», поэтому, если вы хотите, чтобы он заменял всю строку, вам нужно сделать так, чтобы «шаблон» соответствовал всей строке.
Брайан Кэмпбелл

9
Это прерывается, когда вводитсяHere is a String Here is a String
Джей Д.

1
Было бы здорово увидеть решение для случая: «Вот бла-бла. Строка. Вот 1, бла-бла. Строка. Вот 2, бла-бла. Строка. Вот 2, а выводится только первая подстрока между Здесь и Строкой»
Джей Ди

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
Это не работает, потому что, если ваша конечная строка «строка» встречается более одного раза, она получит последнее вхождение, а не следующее .
Баттл Буткус

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)'
анишсане

58

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

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

Основное отличие - добавление .*сразу до Hereи после String.


Ваш ответ многообещающий. Но есть одна проблема. Как я могу извлечь его в первую увиденную строку, если в одной строке есть несколько строк? Спасибо
Миан Асбат Ахмад

@MianAsbatAhmad Вы бы хотели сделать *квантификатор между Hereи Stringне жадным (или ленивым). Однако тип регулярного выражения, используемый sed, не поддерживает ленивые квантификаторы ( ?сразу после .*) в соответствии с этим вопросом Stackoverflow. Обычно для реализации ленивого квантификатора вы просто сопоставляете все, кроме токена, который вы не хотите сопоставить, но в этом случае нет только одного токена, а целой строки String.
Wheeler

Спасибо, я получил ответ с помощью awk, stackoverflow.com/questions/51041463/…
Миан Асбат Ахмад

К сожалению, это не сработает, если в строке есть разрывы строк
Витало Бенисио

Так не должно быть. .не соответствует разрывам строки. Если вы хотите сопоставить разрывы строк, вы можете заменить .что-то вроде [\s\s].
Wheeler

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 with -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: [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 Как я могу заменить новую строку (\ n) с помощью sed?

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.