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

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

Итак, из:

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

Я хочу:

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

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

Я пробовал:

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

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

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

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

Ответ 1

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

perl -pe 's|(http://.*?/).*|\1|'

Ответ 2

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

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

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

Ответ 3

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

Ответ 4

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

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

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

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

Ответ 5

Моделирование ленивого (не жадного) квантификатора в 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
      

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

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

      введите описание изображения здесь

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

    • POSIX BRE

      Regex:

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

      Sed:

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

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

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

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

      введите описание изображения здесь

      Если он найден, другие следующие разряды расходуются и захватываются, а оставшаяся строка соответствует немедленно, так как * означает больше или равно нулю, он пропускает вторую группу захвата \(\([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.

    введите описание изображения здесь

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

    введите описание изображения здесь


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

Используя подход №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/

Ответ 6

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

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

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

НТН!

Ответ 7

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

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

Ответ 8

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

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

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

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

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

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

Ответ 9

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

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

Ответ 10

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

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

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

Ответ 11

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

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

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

-e "s,$,/,"

Ответ 12

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

Ответ 13

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

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

Ответ 14

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

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

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

Ответ 15

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

Ответ 16

Это - то, как надежно сделать не жадное сопоставление многосимвольных строк, используя 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

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

  1. s/@/@A/g; s/{/@B/g; s/}/@C/g s/@/@A/g; s/{/@B/g; s/}/@C/g преобразует { и } в строки-заполнители, которые не могут существовать во входных данных, поэтому эти символы доступны для преобразования в foo и bar.
  2. s/foo/{/g; s/bar/}/g s/foo/{/g; s/bar/}/g конвертирует foo и bar в { и } соответственно
  3. s/{[^{}]*}/<&>/g выполняет операцию, которую мы хотим - преобразование foo...bar в <foo...bar>
  4. s/}/bar/g; s/{/foo/g s/}/bar/g; s/{/foo/g преобразует { и } обратно в foo и bar.
  5. s/@C/}/g; s/@B/{/g; s/@A/@/g s/@C/}/g; s/@B/{/g; s/@A/@/g преобразует строки-заполнители в их исходные символы.

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

Ответ 17

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

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

Ответ 18

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

Ответ 19

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

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

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

Ответ 20

Вот что вы можете сделать с помощью двухэтапного подхода и 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

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

Ответ 21

Еще не видел этот ответ, поэтому вот как вы можете сделать это с помощью vi или vim:

vi -c '%s/\(http:\/\/.\{-}\/\).*/\1/ge | wq' file &>/dev/null

Это запускает подстановку vi :%s глобально (завершающий g), воздерживается от выдачи ошибки, если шаблон не найден (e), затем сохраняет полученные изменения на диск и завершает работу. &>/dev/null предотвращает кратковременное мигание графического интерфейса на экране, что может раздражать.

Мне иногда нравится использовать vi для сверхсложных регулярных выражений, потому что (1) perl умер замертво, (2) vim имеет очень продвинутый движок регулярных выражений, и (3) я уже близко знаком с регулярными выражениями vi в моей повседневной жизни. использование редактирования документов.