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

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

Так с:

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

хочу:

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

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

Я пробовал:

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

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

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

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

20 ответов


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

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

попробовать [^/]* вместо .*?:

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

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

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

выход:

http://www.suon.co.uk

это:

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

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

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

выход:

http://www.suon.co.uk/

sed не поддерживает оператор "non greedy".

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

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

С. П. Нет необходимости в обратной косой черты "/".


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

и все другие вкусы regex!

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

    • POSIX ERE (через )

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

      (EXPRESSION).*|.
      

      Sed:

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

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

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

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

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

      enter image description here

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

    • POSIX BRE

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

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

      Sed:

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

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

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

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

      enter image description here

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

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

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

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

    входной строки:

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

    - EDE:end

    - SDE: start

    $ sed "s/\(end\).*//; s/\(\(start.*\)*.\)*//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 (выражение с разделителями), вы должны выбрать два соответствующих выражения:

  • Эде: [^:/]\/

  • SDE: http:

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

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

выход:

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

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

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

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

HTH!


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

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

sed-не жадный соответствия Кристоф Sieghart

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

жадное сопоставление

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

не жадный, соответствующего

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

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

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

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

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

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


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

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

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


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

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

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

-e "s,$,/,"

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

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

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


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


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

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

$ 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

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

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


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


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

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

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


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

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

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

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