Нежелательное (неохотное) соответствие регулярному выражению в 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|'

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

Всего 20 ответов


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

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

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

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

С 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
  • использование ; search command separator вместо / чтобы было проще набирать так s;<pattern>;<replace>;p
  • помните совпадение между скобками ( ... ) , позже доступными с 1 , 2 ...
  • соответствие http://
  • за которым следует что-либо в скобках [] , [ab/] будет означать либо a либо b или /
  • first ^ in [] означает, что not , за которым следует что-то, кроме вещи в []
  • поэтому [^/] означает что угодно, кроме / character
  • * - повторять предыдущую группу, поэтому [^/]* означает символы, кроме / .
  • пока 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/

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

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

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

PS нет необходимости обратного слэша «/».


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

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

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

    • 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,}).*)* и достигает точки . для соответствия одному символу, и этот процесс продолжается.

  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).* Сопоставляет и фиксирует конец конечного разделителя и заменяет все совпадения с последними захваченными символами, которые являются конечным разделителем. На этом этапе наш выход: foobar start block #1 end .

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

    Затем результат передается во второе regex ((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/

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

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

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

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

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

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

НТН!


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

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

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

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

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

% 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 $1,$2,$3}' 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/"

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

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


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

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

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

-e "s,$,/,"

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

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

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


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

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

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


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

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

$ 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|(.*)/.*|1|'

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


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


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

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

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


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

 A=http://www.suepearson.co.uk/product/174/71/3816/ echo $A|awk ' { var=gensub(///,"||",3,$0) ; sub(/||.*/,"",var); print var }' к 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

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


Есть идеи?

10000