Вопрос по greedy, sed, regex, pcre, regex-greedy – Не жадное (неохотное) сопоставление регулярных выражений в sed?

364

Я пытаюсь использовать sed для очистки строк URL, чтобы извлечь только домен.

Так из:

http://www.suepearson.co.uk/product/174/71/3816/

Я хочу:

http://www.suepearson.co.uk/

(с косой чертой или без нее, это не имеет значения)

Я пытался:

 sed 's|\(http:\/\/.*?\/\).*|\1|'

и (избегая не жадного квантификатора)

sed 's|\(http:\/\/.*\?\/\).*|\1|'

но я не могу заставить работать не жадный квантификатор, поэтому он всегда совпадает со всей строкой.

@AttishOculus Первый символ после 's'; в выражении подстановки в sed есть разделитель. Следовательно, 's ^ foo ^ bar ^' или 's! foo! bar!' также работа Squidly
Примечание: если вы разграничиваете свои регулярные выражения с помощью «|», вам не нужно избегать символов «/». На самом деле, большинство людей разделяют символом & quot; | & quot; вместо "/", чтобы избежать "заборов". AttishOculus
Для расширенного регулярного выражения используйтеsed -E 's..., Тем не менее, неохотный оператор. Ondra Žižka

Ваш Ответ

20   ответов
0

sed 's|\(http:\/\/www\.[a-z.0-9]*\/\).*|\1| тоже работает

3

е) регулярные выражения

Обновление: -E в MacOS X, -r в GNU sed.

Нет, это не так ... По крайней мере, GNU sed.
Это правильный ответ, если вы хотите использовать sed, и он наиболее применим к первоначальному вопросу.
GNU SED признан-E как недокументированный вариант на некоторое время, но вrelease 4.2.2.177документация была обновлена, чтобы отразить это, поэтому-E хорошо для обоих сейчас.
В более широком смысле,-E уникален для BSDsed и, следовательно, OS X. Ссылки на справочные страницы.-r приносит расширенные регулярные выраженияGNU sed как отмечено в коррекции @ stephancheg. Остерегайтесь при использовании команды известной изменчивости между дистрибутивами nix. Я узнал, что трудный путь.
GNU sed-r опция только изменяет правила выхода, в соответствии сAppendix A Extended regular expressions информационного файла и несколько быстрых тестов; он фактически не добавляет не жадный квалификатор (по состоянию наGNU sed version 4.2.1 по крайней мере.)
36

Вы должны использовать & quot; [] & quot; оператор для исключения & quot; / & quot; из матча

sed 's,\(http://[^/]*\)/.*,\1,'

Постскриптум нет необходимости использовать обратную косую черту "/".

на самом деле, нет. если разделитель может быть одним из многих возможных символов (скажем, только в виде строки чисел), ваше совпадение отрицания может становиться все более и более сложным. это хорошо, но было бы неплохо иметь возможность сделать. * не жадный
Вопрос был более общим. Эти решения работают для URL-адресов, но не (например) для моего случая использования конечных нулей.s/([[:digit:]]\.[[1-9]]*)0*/\1/ очевидно, не будет работать хорошо для1.20300, Поскольку первоначальный вопрос касался URL-адресов, их следует указать в принятом ответе.
Это должен быть принятый ответ
3
sed 's|(http:\/\/[^\/]+\/).*|\1|'
Если вы используете & quot; | & quot; как ваш разделитель, вам не нужно экранировать "/".
25
Simulating lazy (un-greedy) quantifier in sed

Finding first occurrence of an expression:

POSIX ERE (using -r option)

Regex:

(EXPRESSION).*|.

Sed:

sed -r "s/(EXPRESSION).*|./\1/g" # Global `g` modifier should be on

Example (finding first sequence of digits) Live demo:

$ sed -r "s/([0-9]+).*|./\1/g" <<< "foo 12 bar 34"
12

How does it work?

This regex benefits from an alternation |. At each position engine will look for the first side of alternation (our target) and if it is not matched second side of alternation which has a dot . matches the next immediate character.

enter image description here

Since global flag is set, engine tries to continue matching character by character up to the end of input string or our target. As soon as the first and only capturing group of left side of alternation is matched (EXPRESSION) rest of line is consumed immediately as well .*. We now hold our value in the first capturing group.

POSIX BRE

Regex:

\(\(\(EXPRESSION\).*\)*.\)*

Sed:

sed "s/\(\(\(EXPRESSION\).*\)*.\)*/\3/"

Example (finding first sequence of digits):

$ sed "s/\(\(\([0-9]\{1,\}\).*\)*.\)*/\3/" <<< "foo 12 bar 34"
12

This one is like ERE version but with no alternation involved. That's all. At each single position engine tries to match a digit.

enter image description here

If it is found, other following digits are consumed and captured and the rest of line is matched immediately otherwise since * means more or zero it skips over second capturing group \(\([0-9]\{1,\}\).*\)* and arrives at a dot . to match a single character and this process continues.

Finding first occurrence of a delimited expression:

This approach will match the very first occurrence of a string that is delimited. We can call it a block of string.

sed "s/\(END-DELIMITER-EXPRESSION\).*/\1/; \
     s/\(\(START-DELIMITER-EXPRESSION.*\)*.\)*/\1/g"

Input string:

foobar start block #1 end barfoo start block #2 end

-EDE: end

-SDE: start

$ sed "s/\(end\).*/\1/; s/\(\(start.*\)*.\)*/\1/g"

Output:

start block #1 end

First regex \(end\).* matches and captures first end delimiter end and substitues all match with recent captured characters which is the end delimiter. At this stage our output is: foobar start block #1 end.

enter image description here

Then the result is passed to second regex \(\(start.*\)*.\)* that is same as POSIX BRE version above. It matches a single character if start delimiter start is not matched otherwise it matches and captures the start delimiter and matches the rest of characters.

enter image description here

Directly answering your question

Используя подход № 2 (выражение с разделителями), вы должны выбрать два подходящих выражения:

EDE: [^:/]\/

SDE: http:

Использование:

$ sed "s/\([^:/]\/\).*/\1/g; s/\(\(http:.*\)*.\)*/\1/" <<< "http://www.suepearson.co.uk/product/174/71/3816/"

Выход:

http://www.suepearson.co.uk/
Ого, я добавлю этот ответ в список моих любимых! & # XF118;
4

Несмотря на то, что это не универсальное решение, в некоторых случаях вы можете использовать & quot; циклы & quot; чтобы удалить все ненужные части строки следующим образом:

sed -r -e ":loop" -e 's|(http://.+)/.*|\1|' -e "t loop"
-r: Use extended regex (for + and unescaped parenthesis) ":loop": Define a new label named "loop" -e: add commands to sed "t loop": Jump back to label "loop" if there was a successful substitution

Единственная проблема здесь заключается в том, что он также обрезает последний символ-разделитель ("/"), но если он вам действительно нужен, вы все равно можете просто вернуть его после "цикла". готово, просто добавьте эту дополнительную команду в конце предыдущей командной строки:

-e "s,$,/,"
0

Еще одна версия sed:

sed 's|/[:alphanum:].*||' file.txt

Это соответствует/ сопровождаемый буквенно-цифровым символом (таким образом, не другой слеш), а также остальными символами до конца строки. После этого он заменяет его ничем (т.е. удаляет его).

218

Пытаться[^/]* вместо.*?:

sed 's|\(http://[^/]*/\).*|\1|g'
Большое спасибо ... поскольку Perl больше не является базой установки по умолчанию во многих дистрибутивах Linux!
Как сделать, чтобы sed соответствовал не жадной фразе, используя эту технику?
К сожалению, вы не можете & t; увидетьchaos’s answer.
0

A=http://www.suepearson.co.uk/product/174/71/3816/  
echo $A|awk '  
{  
  var=gensub(///,"||",3,$0) ;  
  sub(/\|\|.*/,"",var);  
  print var  
}'  

Output: http://www.suepearson.co.uk

Надеюсь, это поможет!

108

ища что-либо, кроме разделителя до разделителя:

echo "http://www.suon.co.uk/product/1/7/3/" | sed -n 's;\(http://[^/]*\)/.*;\1;p'

Выход:

http://www.suon.co.uk

это:

don't output -n search, match pattern, replace and print s/<pattern>/<replace>/p use ; search command separator instead of / to make it easier to type so s;<pattern>;<replace>;p remember match between brackets \( ... \), later accessible with \1,\2... match http:// followed by anything in brackets [], [ab/] would mean either a or b or / first ^ in [] means not, so followed by anything but the thing in the [] so [^/] means anything except / character * is to repeat previous group so [^/]* means characters except /. so far sed -n 's;\(http://[^/]*\) means search and remember http://followed by any characters except / and remember what you've found we want to search untill the end of domain so stop on the next / so add another / at the end: sed -n 's;\(http://[^/]*\)/' but we want to match the rest of the line after the domain so add .* now the match remembered in group 1 (\1) is the domain so replace matched line with stuff saved in group \1 and print: sed -n 's;\(http://[^/]*\)/.*;\1;p'

Если вы хотите включить обратную косую черту и после домена, добавьте еще одну обратную косую черту в группу, чтобы запомнить:

echo "http://www.suon.co.uk/product/1/7/3/" | sed -n 's;\(http://[^/]*/\).*;\1;p'

выход:

http://www.suon.co.uk/
Можно ли заменить разделитель на строку?
Относительно недавних правок: круглые скобки являются своего рода символом, заключенным в скобки, поэтому их неправильно называть скобками, особенно если вы следите за словом с реальными символами, как это сделал автор. Кроме того, это предпочтительное использование в некоторых культурах, поэтому замена его на предпочтительное использование в вашей собственной культуре кажется немного грубым, хотя я уверен, что это не то, что задумал редактор. Лично я думаю, что лучше использовать чисто описательные имена, такие какround brackets, square brackets, а такжеangle brackets.
381

ни расширенное регулярное выражение Posix / GNU не распознает не жадный квантификатор; вам нужно позднее регулярное выражение К счастью, Perl регулярное выражение для этого контекста довольно легко получить:

perl -pe 's|(http://.*?/).*|\1|'
Для этого на месте используйте варианты-pi -e.
@ dolphus333: & quot; Ни базовое, ни расширенное регулярное выражение Posix / GNU не распознает не жадный квантификатор & quot; означает "вы не можете использовать негладкий квантификатор в sed".
@Freedom_Ben: IIRCperl являетсяrequired по POSIX
Черт возьми, я не могу поверить, что это сработало :-) Единственное, что отстой - теперь мой скрипт имеет зависимость от Perl :-( С другой стороны, практически в каждом дистрибутиве Linux есть Perl, так что, вероятно, это не проблема :-)
@ S & # xE9; rgio Это то, как вы делаете запрошенную вещь, что невозможно вsedиспользуя синтаксис, в основном идентичный синтаксисуsed
20
Non-greedy solution for more than a single character

но я предполагаю, что людям она все еще нужна. Допустим, вы хотите убить все до самого первого появленияHELLO, Ты не можешь сказать[^HELLO]...

Таким образом, хорошее решение состоит из двух шагов, при условии, что вы можете сэкономить уникальное слово, которое вы не ожидаете во входных данных, скажем,top_sekrit.

В этом случае мы можем:

s/HELLO/top_sekrit/     #will only replace the very first occurrence
s/.*top_sekrit//        #kill everything till end of the first HELLO

Конечно, при более простом вводе вы можете использовать меньшее слово или, может быть, даже один символ.

НТН!

Спасибо, правильно, легче понять :-)
Чтобы сделать его еще лучше, полезно в ситуации, когда вы не можете ожидать, что неиспользуемый символ: 1. замените этот специальный символ на действительно неиспользуемое СЛОВО, 2. замените конечную последовательность специальным символом, 3. выполните поиск, заканчивающийся специальным символом, 4 Заменить специальный символ назад, 5. Заменить специальный WORD обратно. Например, вам нужен жадный оператор между & lt; hello & gt; и & lt; / hello & gt ;:
Согласен. Хорошее решение. Я бы перефразировал комментарий следующим образом: если вы не можете полагаться на то, что ~ не используется, сначала замените его текущие вхождения, используя s / ~ / VERYspeciaL / g, затем выполните вышеуказанный трюк, а затем верните оригинал ~, используя s / VERYspeciaL / ~ / g.
Вот пример: echo & quot; Find: & lt; hello & lt; br & gt; yes & lt; / hello & gt; & Lt; & привет GT; сек ~ & Зонд л; / & привет GT; & Quot; | sed -e & quot; s, ~, VERYSPECIAL, g & quot; -e 's, & lt; / hello & gt;, ~, g & quot; -e & quot; s,. * Найти: & lt; hello & gt; ([^ ~] *). *, \ 1, & quot; -e 's, \ ~, / llo; / hello & gt;, & quot; -e & quot; s, VERYSPECIAL, ~, & quot;
Я предпочитаю использовать более редкие «переменные» для такого рода вещей, поэтому вместо`Я буду использовать<$$> (поскольку$$ расширяется до идентификатора вашего процесса в оболочке, хотя вам нужно использовать двойные кавычки, а не одинарные, и это может нарушить другие части вашего регулярного выражения) или, если доступен Юникод, что-то вроде<∈∋>.
1

как надежно выполнить не жадное сопоставление многосимвольных строк, используя sed. Допустим, вы хотите изменить каждыйfoo...bar в<foo...bar> так, например, этот вход:

$ cat file
ABC foo DEF bar GHI foo KLM bar NOP foo QRS bar TUV

должен стать этот вывод:

ABC <foo DEF bar> GHI <foo KLM bar> NOP <foo QRS bar> TUV

Для этого вы конвертируете foo и bar в отдельные символы, а затем используете отрицание этих символов между ними:

$ sed 's/@/@A/g; s/{/@B/g; s/}/@C/g; s/foo/{/g; s/bar/}/g; s/{[^{}]*}/<&>/g; s/}/bar/g; s/{/foo/g; s/@C/}/g; s/@B/{/g; s/@A/@/g' file
ABC <foo DEF bar> GHI <foo KLM bar> NOP <foo QRS bar> TUV

В приведенном выше:

s/@/@A/g; s/{/@B/g; s/}/@C/g is converting { and } to placeholder strings that cannot exist in the input so those chars then are available to convert foo and bar to. s/foo/{/g; s/bar/}/g is converting foo and bar to { and } respectively s/{[^{}]*}/<&>/g is performing the op we want - converting foo...bar to <foo...bar> s/}/bar/g; s/{/foo/g is converting { and } back to foo and bar. s/@C/}/g; s/@B/{/g; s/@A/@/g is converting the placeholder strings back to their original characters.

Обратите внимание, что вышеупомянутое не зависит от какой-либо конкретной строки, отсутствующей во входных данных, поскольку она производит такие строки на первом шаге, и не заботится о том, какое вхождение какого-либо конкретного регулярного выражения вы хотите сопоставить, так как вы можете использовать{[^{}]*} столько раз, сколько необходимо в выражении, чтобы выделить фактическое совпадение, которое вы хотите, и / или с помощью оператора числового совпадения seds, например, заменить только второе вхождение:

$ sed 's/@/@A/g; s/{/@B/g; s/}/@C/g; s/foo/{/g; s/bar/}/g; s/{[^{}]*}/<&>/2; s/}/bar/g; s/{/foo/g; s/@C/}/g; s/@B/{/g; s/@A/@/g' file
ABC foo DEF bar GHI <foo KLM bar> NOP foo QRS bar TUV
2

что пытаетесь использовать sed (вместо perl, cut и т. Д.), Попробуйте группировать. Это позволяет обойтись без жадного идентификатора, который может быть не распознан. Первая группа - это протокол (то есть «http: //», «https: //», «tcp: //» и т. Д.). Вторая группа - это домен:

echo "http://www.suon.co.uk/product/1/7/3/" | sed "s|^\(.*//\)\([^/]*\).*$|\1\2|"

Если вы не знакомы с группировкой, начнитеВот.

0
echo "/home/one/two/three/myfile.txt" | sed 's|\(.*\)/.*|\1|'

так что вы получите жадный матч/home/one/two/three/, если вы добавите еще/ лайк/home/one/two/three/four/myfile.txt вы будете жадно соответствоватьfour также:/home/one/two/three/fourвопрос о не жадных
14

sed - не жадное совпадение от Christoph Sieghart

Уловка, чтобы получить не жадное соответствие в sed, состоит в том, чтобы сопоставить все символы, кроме того, который завершает сравнение. Я знаю, нетрудно, но я потратил драгоценные минуты на это, и сценарии оболочки должны быть, в конце концов, быстрыми и легкими. Так что в случае, если это может понадобиться кому-то другому:

Жадный подход

% echo "<b>foo</b>bar" | sed 's/<.*>//g'
bar

Не жадное совпадение

% echo "<b>foo</b>bar" | sed 's/<[^>]*>//g'
foobar
5

sed конечно есть свое место но это не один из них!

Как указал Ди: просто используйтеcut, В этом случае все гораздо проще и безопаснее. Вот пример, где мы извлекаем различные компоненты из URL, используя синтаксис Bash:

url="http://www.suepearson.co.uk/product/174/71/3816/"

protocol=$(echo "$url" | cut -d':' -f1)
host=$(echo "$url" | cut -d'/' -f3)
urlhost=$(echo "$url" | cut -d'/' -f1-3)
urlpath=$(echo "$url" | cut -d'/' -f4-)

дает тебе:

protocol = "http"
host = "www.suepearson.co.uk"
urlhost = "http://www.suepearson.co.uk"
urlpath = "product/174/71/3816/"

Как видите, это гораздо более гибкий подход.

(все заслуги перед Ди)

16

echo "http://www.suepearson.co.uk/product/174/71/3816/" | cut -d'/' -f1-3
1

Поскольку полное доменное имя не должно превышать общую длину 253 символов, замените. * На. \ {1, 255 \}

9

не используя регулярное выражение, это использовать метод fields / delimiter, например

string="http://www.suepearson.co.uk/product/174/71/3816/"
echo $string | awk -F"/" '{print $1,$2,$3}' OFS="/"

Похожие вопросы