Не жадное (неохотное) регулярное выражение в sed?
Я пытаюсь использовать sed для очистки строк URL-адресов, чтобы извлечь только домен..
Так с:
http://www.suepearson.co.uk/product/174/71/3816/
хочу:
(с косой чертой или без нее, это не имеет значения)
Я пробовал:
sed 's|(http://.*?/).*||'
и (избегая не жадного квантора)
sed 's|(http://.*?/).*||'
но я не могу заставить не жадный Квантор работать, поэтому он всегда в конечном итоге соответствует всей строке.
20 ответов
ни основное, ни расширенное регулярное выражение Posix/GNU не распознают не-жадный Квантор; вам нужно более позднее регулярное выражение. К счастью, Perl regex для этого контекста довольно легко получить:
perl -pe 's|(http://.*?/).*||'
С 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!
-
поиск первого вхождения выражения:
-
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
как это работает?
это регулярное выражение выигрывает от чередования
|
. В каждой позиции двигатель будет искать первую сторону чередования (нашу цель), а если она не соответствует второй стороне чередования, которая имеет точку.
соответствует следующему непосредственному символу.поскольку установлен глобальный флаг, двигатель пытается продолжайте сопоставлять символ за символом до конца входной строки или нашей цели. Как только первая и единственная группа захвата левой стороны чередования будет сопоставлена
(EXPRESSION)
остальная часть линии потребляется немедленно, а также.*
. Теперь наша ценность в первой группе захвата. -
POSIX BRE
регулярное выражение:
\(\(\(EXPRESSION\).*\)*.\)*
Sed:
sed "s/\(\(\(EXPRESSION\).*\)*.\)*//"
пример (поиск первой последовательности цифры):
$ sed "s/\(\(\([0-9]\{1,\}\).*\)*.\)*//" <<< "foo 12 bar 34"
12
это похоже на версию ERE, но без чередования. Вот и все. В каждой отдельной позиции движок пытается соответствовать цифре.
если он найден, другие следующие цифры потребляются и захватываются, а остальная часть строки сопоставляется немедленно в противном случае, так как
*
означает больше или равно нулю он пропускает вторую группу захвата\(\([0-9]\{1,\}\).*\)*
и прибывает в точку!--17--> чтобы соответствовать одному символу, и этот процесс продолжается.
-
-
поиск первого появления разделителями выражение:
этот подход будет соответствовать самому первому вхождению строки, которая разделена. Мы можем назвать это блоком строк.
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
.затем результат передается во второе регулярное выражение
\(\(start.*\)*.\)*
это то же самое, что и версия POSIX BRE выше. Он соответствует одному символу если начать разделительstart
не соответствует в противном случае он соответствует и захватывает разделитель начала и соответствует остальным символам.
прямой ответ на ваш вопрос
Используя подход #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-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
выше:
-
s/@/@A/g; s/{/@B/g; s/}/@C/g
преобразование{
и}
для строк-заполнителей, которые не могут существовать во входных данных, поэтому эти символы доступны для преобразованияfoo
иbar
to. -
s/foo/{/g; s/bar/}/g
преобразованиеfoo
иbar
to{
и}
соответственно -
s/{[^{}]*}/<&>/g
выполняет op, который мы хотим-преобразованиеfoo...bar
до<foo...bar>
-
s/}/bar/g; s/{/foo/g
преобразование{
и}
наfoo
иbar
. -
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:
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
надеюсь, что это поможет!