Не жадное (неохотное) сопоставление регулярных выражений в sed?

284

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

Итак, из:

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

Я хочу:

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

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

Я пробовал:

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

и (ускользание от неживого квантора)

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

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

  • 49
    Примечание: если вы разграничиваете свои регулярные выражения с помощью «|», вам не нужно избегать «/». На самом деле, большинство людей разделяют знаком «|» вместо "/", чтобы избежать "заборов".
  • 11
    @AttishOculus Первый символ после 's' в выражении замены в sed - это разделитель. Следовательно 's ^ foo ^ bar ^' или 's! Foo! Bar!' также работа
Показать ещё 1 комментарий
Теги:
sed
greedy
pcre
regex-greedy

19 ответов

294
Лучший ответ

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

perl -pe 's|(http://.*?/).*|\1|'
  • 9
    Для этого используйте параметры -pi -e .
  • 10
    Черт возьми, я не могу поверить, что это сработало :-) Единственное, что отстой - теперь мой скрипт имеет зависимость от Perl :-( С другой стороны, практически в каждом дистрибутиве Linux есть Perl, так что, вероятно, это не проблема :-)
Показать ещё 9 комментариев
186

Попробуйте [^/]* вместо .*?:

sed 's|\(http://[^/]*/\).*|\1|g'
  • 3
    Как сделать, чтобы sed соответствовал не жадной фразе с помощью этой техники?
  • 5
    К сожалению, вы не можете; см . ответ хаоса
Показать ещё 2 комментария
79

С 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 или /
  • первый ^ в [] означает 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/
  • 7
    Относительно недавних правок: круглые скобки являются своего рода символом в скобках, поэтому их нельзя называть скобками, особенно если вы следите за словом с реальными символами, как это сделал автор. Кроме того, это предпочтительное использование в некоторых культурах, поэтому замена его на предпочтительное использование в вашей собственной культуре кажется немного грубым, хотя я уверен, что это не то, что задумал редактор. Лично я думаю, что лучше использовать чисто описательные имена, такие как круглые , квадратные и угловые скобки .
  • 1
    Можно ли заменить разделитель на строку?
27

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

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

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

P.S. нет необходимости обратного слэш "/".

  • 2
    Это должен быть принятый ответ
  • 0
    на самом деле, нет. если разделитель может быть одним из многих возможных символов (скажем, только в виде строки чисел), ваше совпадение отрицания может становиться все более и более сложным. это хорошо, но было бы неплохо иметь возможность сделать. * не жадный
Показать ещё 1 комментарий
15

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

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

Итак, хорошее решение включает в себя два шага, предполагая, что вы можете зарезервировать уникальный символ, которого вы не ожидаете на входе, например ` (обратная сторона).

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

s_HELLO_`_     #will only replace the very first occurrence
s_.*`__        #kill everything till end of the first HELLO

НТН!

  • 4
    Чтобы сделать его еще лучше, полезно в ситуации, когда вы не можете ожидать, что неиспользуемый символ: 1. замените этот специальный символ на действительно неиспользуемое СЛОВО, 2. замените конечную последовательность специальным символом, 3. выполните поиск, заканчивающийся специальным символом, 4 Заменить специальный символ назад, 5. Заменить специальный СЛОВО обратно. Например, вам нужен жадный оператор между <hello> и </ hello>:
  • 3
    Вот пример: echo "Найти: <hello> fir ~ st <br> yes </ hello> <hello> sec ~ ond </ hello>" | sed -e "s, ~, VERYSPECIAL, g" -e "s, </ hello>, ~, g" -e "s,. * Найти: <hello> ([^ ~] *). *, \ 1 , "-e", \ ~, </ hello>, "-e", VERYSPECIAL, ~, "
Показать ещё 5 комментариев
12

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

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

Моделирование ленивого (не жадного) квантификатора в sed

И все другие ароматы регулярных выражений!

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

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

      Regex:

      (EXPRESSION).*|.
      

      Sed:

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

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

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

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

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

      Изображение 4745

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

    • POSIX BRE

      Regex:

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

      Sed:

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

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

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

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

      Изображение 4746

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

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

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

    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.

    Изображение 4747

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

    Изображение 4748


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

Используя подход №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/
  • 3
    Вау, я добавлю этот ответ в избранное! 
8

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

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

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

Как вы можете видеть, это намного более гибкий подход.

(все кредиты Ди)

3

sed -E интерпретирует регулярные выражения как расширенные (современные) регулярные выражения

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

  • 4
    Нет, это не так ... По крайней мере, не GNU sed.
  • 7
    В более широком смысле -E уникален для BSD sed и, следовательно, для OS X. Ссылки на справочные страницы. -r вносит расширенные регулярные выражения в GNU sed как отмечалось в исправлении @ stephancheg. Остерегайтесь при использовании команды известной изменчивости между дистрибутивами 'nix. Я узнал, что трудный путь.
Показать ещё 4 комментария
2

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

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

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

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

Нежелательное соответствие

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

Есть еще надежда решить эту проблему с помощью чистого (GNU) sed. Несмотря на то, что это не общее решение, в некоторых случаях вы можете использовать "петли", чтобы исключить все ненужные части строки, например:

sed -r -e ":loop" -e 's|(http://.+)/.*|\1|' -e "t loop"
  • -r: использовать расширенное регулярное выражение (для + и неэкранированных скобок)
  • ": loop": определить новый ярлык с именем "loop"
  • -e: добавить команды в sed
  • "t loop": вернитесь к метке "loop", если была успешная замена

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

-e "s,$,/,"
2
sed 's|(http:\/\/[^\/]+\/).*|\1|'
  • 1
    Если вы используете "|" как ваш разделитель, вам не нужно экранировать "/".
1

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

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

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

0

Вот что вы можете сделать с помощью двухэтапного подхода и awk:

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

Вывод: http://www.suepearson.co.uk

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

0

Другая версия sed:

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

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

0

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

0

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

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

Надеюсь, я получил его на другом форуме:)

  • 4
    так что вы получите жадное совпадение: /home/one/two/three/ , если вы добавите другое / like /home/one/two/three/four/myfile.txt вы тоже жадно сопоставите four : /home/one/two/three/four , вопрос о не жадных

Ещё вопросы

Сообщество Overcoder
Наверх
Меню