Не жадное (неохотное) соответствие регулярных выражений в sed? - PullRequest
376 голосов
/ 09 июля 2009

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

Так от:

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

Я хочу:

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

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

Я пытался:

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

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

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

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

Ответы [ 21 ]

393 голосов
/ 09 июля 2009

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

perl -pe 's|(http://.*?/).*|\1|'
227 голосов
/ 09 июля 2009

В этом конкретном случае вы можете выполнить работу без использования не жадного регулярного выражения.

Попробуйте это не жадное регулярное выражение [^/]* вместо .*?:

sed 's|\(http://[^/]*/\).*|\1|g'
112 голосов
/ 21 декабря 2012

С помощью sed я обычно реализую не жадный поиск, ища что-либо, кроме разделителя до разделителя:

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

Выход:

http://www.suon.co.uk

это:

  • не выводить -n
  • поиск, сопоставление, замена и печать s/<pattern>/<replace>/p
  • используйте ; разделитель команд поиска вместо /, чтобы упростить ввод, поэтому s;<pattern>;<replace>;p
  • запомнить совпадение в скобках \( ... \), позднее доступно с \1, \2 ...
  • совпадение http://
  • , за которым следует что-либо в скобках [], [ab/] будет означать либо a, либо b, либо /
  • first ^ in [] означает not, поэтому за ним следует что угодно, кроме вещи в []
  • поэтому [^/] означает все, кроме / символа
  • * - повторить предыдущую группу, поэтому [^/]* означает символы, кроме /.
  • пока sed -n 's;\(http://[^/]*\) означает поиск и запоминание http://, за которым следуют любые символы, кроме /, и запомните, что вы нашли
  • мы хотим выполнить поиск до конца домена, поэтому остановитесь на следующем /, поэтому добавьте еще / в конце: sed -n 's;\(http://[^/]*\)/', но мы хотим сопоставить остальную часть строки после домена, поэтому добавьте .*
  • теперь совпадение, запомненное в группе 1 (\1), является доменом, поэтому замените согласованную строку вещами, сохраненными в группе \1, и выведите: 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/
36 голосов
/ 09 июля 2009

sed не поддерживает «не жадный» оператор.

Вы должны использовать оператор "[]", чтобы исключить "/" из совпадения.

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

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

26 голосов
/ 28 сентября 2016

Имитация ленивого (не жадного) квантификатора в sed

И все другие регулярные выражения!

  1. Поиск первого вхождения выражения:

    • POSIX ERE (с использованием опции -r)

      Regex:

      (EXPRESSION).*|.
      

      Сед:

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

      Пример (поиск первой последовательности цифр) Живая демоверсия :

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

      Как это работает ?

      Это регулярное выражение имеет преимущество от чередования |. В каждой позиции двигатель будет искать первую сторону чередования (наша цель), и, если она не совпадает, вторая сторона чередования с точкой . соответствует следующему непосредственному символу.

      enter image description here

      Поскольку установлен глобальный флаг, движок пытается продолжить сопоставление символа за символом до конца входной строки или нашей цели. Как только первая и единственная группа захвата левой стороны чередования совпадает (EXPRESSION), остальная часть линии сразу же расходуется .*. Теперь мы держим наше значение в первой группе захвата.

    • POSIX BRE

      Regex:

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

      Сед:

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

      Пример (поиск первой последовательности цифр):

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

      Это похоже на версию ERE, но без чередования. Это все. В каждой позиции двигатель пытается найти цифру.

      enter image description here

      Если он найден, другие следующие цифры потребляются и захватываются, а остальная часть строки сопоставляется немедленно, иначе * означает больше или ноль он пропускает вторую группу захвата \(\([0-9]\{1,\}\).*\)* и достигает точки . для соответствия одному символу, и этот процесс продолжается.

  2. Поиск первого вхождения выражения с разделителями :

    Этот подход будет соответствовать самому первому вхождению строки с разделителями. Мы можем назвать это блоком строк.

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

    Входная строка:

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

    -EDE: end

    -SDE: start

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

    Выход:

    start block #1 end
    

    Первое регулярное выражение \(end\).* соответствует и захватывает первый конечный разделитель end и заменяет все совпадения на последние захваченные символы, которые является конечным разделителем. На данном этапе наш вывод: foobar start block #1 end.

    enter image description here

    Затем результат передается второму регулярному выражению \(\(start.*\)*.\)*, которое совпадает с версией POSIX BRE выше. Соответствует одному символу если начальный разделитель start не совпадает, в противном случае он совпадает и захватывает начальный разделитель и соответствует остальным символам.

    enter image description here


Непосредственно отвечая на ваш вопрос

Используя подход № 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/
21 голосов
/ 30 октября 2013

Нежадное решение для более чем одного символа

Эта ветка действительно старая, но я полагаю, что людям она все еще нужна. Допустим, вы хотите убить все до самого первого появления 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

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

НТН!

16 голосов
/ 10 декабря 2010

Это можно сделать с помощью cut:

echo "http://www.suepearson.co.uk/product/174/71/3816/" | cut -d'/' -f1-3
15 голосов
/ 13 октября 2017

sed - не жадное совпадение Кристофа Зигхарта

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

Жадное совпадение

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

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

% echo "<b>foo</b>bar" | sed 's/<[^>]*>//g'
foobar
9 голосов
/ 09 июля 2009

Другой способ, не использующий регулярное выражение, - использовать метод fields / delimiter, например,

string="http://www.suepearson.co.uk/product/174/71/3816/"
echo $string | awk -F"/" '{print $1,$2,$3}' OFS="/"
5 голосов
/ 30 августа 2013

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/"

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

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

...