Я должен изменить html-подобный текст с помощью команды sed. Я должен удалить подстроки, начинающиеся с одного или нескольких символов <, затем иметь 0 или более вхождений любых символов, кроме угловых скобок, а затем любые 1 или несколько символов >.

Например: из aaa<bbb>ccc Я хотел бы получить aaaccc

Я могу сделать это с

"s/<[^>]\+>//g"

Но эта команда не работает, если между <> символами есть пустая строка или если в тексте есть двойной <<>>. Например, из

aa<>bb<cc>vv<<gg>>h

Я получаю

aa<>bbvv>h

Вместо

aabbvvh

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

1
Anna 6 Май 2020 в 11:23

2 ответа

Лучший ответ

Вы можете использовать

sed 's/<\+[^>]*>\+//g'
sed 's/<\{1,\}[^>]*>\{1,\}//g'
sed -E 's/<+[^>]*>+//g'

Шаблоны соответствуют

  • <\+ / <\{1,\} - 1 или более вхождений символа <
  • [^>]* - выражение в скобках с отрицанием, которое соответствует 0 или более символам, отличным от >
  • >\+ / >\{1,\} - 1 или более вхождений символа >

Обратите внимание, что в последнем примере POSIX ERE неэкранированный + является квантификатором, совпадающим с 1 или более экземплярами, так же, как \+ в шаблоне POSIX BRE.

Смотрите онлайн sed демонстрацию:

s='aa<>bb<cc>vv<<gg>>h'
sed 's/<\+[^>]*>\+//g' <<< "$s"
sed 's/<\{1,\}[^>]*>\{1,\}//g' <<< "$s"
sed -E 's/<+[^>]*>+//g' <<< "$s"

Результатом каждой команды sed является aabbvvh.

1
Wiktor Stribiżew 6 Май 2020 в 08:27

Проблема заключается в том, что после того, как вы разрешите вложение символов < и >, вы преобразуете тип языка из «обычный» в «свободный от контекста» ,

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

Но есть несколько сложный обходной путь: если вы считаете, что существует верхний предел уровня вложенности, который вы допускаете в тексте, с которым вы сталкиваетесь, то вы можете преобразовать его в обычный язык, который не является основанным на предпосылке, что < em> Нерегулярные случаи никогда не произойдут:

Предположим, у вас никогда не будет более трех уровней вложения в ваш шаблон (это позволяет вам увидеть шаблон и расширить его до N уровней), вы можете использовать следующий алгоритм для построения регулярного выражения, которое позволит вам соответствует три уровня вложенности, но не более (вы можете сделать регулярное выражение для разбора N уровней, но не более того, это umbounded bounded природа регулярных выражений :)).

Давайте построим выражение рекурсивно снизу вверх. Имея только один уровень вложенности, у вас есть только < и >, и вы не можете найти ни один из них внутри (если вы разрешите <, вы разрешите больше уровней вложенности, что запрещено на уровне 0 ) :

{l0} = [^<>]*

Строка, не содержащая символов < и >.

Соответствующий текст будет из этого класса строк, окруженных парой символов < и >:

{l1} = <[^<>]*>

Теперь вы можете построить второй уровень вложенности, чередуя {l0}{l1}{l0}{l1}...{l0} (то есть {l0}({l1}{l0})* и окружая все это < и >, чтобы построить {{X4} }

{l2} = <{l0}({l1}{l0})*> = <[^<>]*(<[^<>]*>[^<>]*)*>

Теперь вы можете построить третий, чередуя последовательности {l0} и {l2} в паре скобок ... (помните, что {l-i} представляет регулярное выражение, которое позволяет до i уровни вложенности или меньше)

{l3} = <{l0}({l2}{l0})*> = <[^<>]*(<[^<>]*(<[^<>]*>[^<>]*)*>[^<>]*)*>

И так далее, последовательно вы формируете последовательность

{lN} = <{l0}({l(N-1)}{l0})*>

И остановитесь, если считаете, что во входном файле не будет более глубоких вложений.

Итак, ваш уровень три регулярного выражения:

<[^<>]*(<[^<>]*(<[^<>]*>[^<>]*)*>[^<>]*)*>
{l3--------------------------------------}
<{l0--}({l2---------------------}{l0--})*>
        <{l0--}({l1----}{l0--})*>
                <{l0--}>          

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

См. демонстрационную версию.

НОТА

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

package com.stackoverflow.q61630608;

import java.util.regex.Pattern;

public class NestingRegex {

    public static String build_regexp( char left, char right, int level ) {
        return level == 0
                ? "[^" + left + right + "]*"
                : level == 1
                        ? left + build_regexp( left, right, 0 ) + right
                        : left + build_regexp( left, right, 0 )
                        + "(" + build_regexp( left, right, level - 1 )
                        + build_regexp( left, right, 0 )
                        + ")*" + right;
    }

    public static void main( String[] args ) {
        for ( int i = 0; i < 5; i++ )
            System.out.println( "{l" + i + "} = "
                    + build_regexp( '<', '>', i ) );
        Pattern pat = Pattern.compile( build_regexp( '<', '>', 16 ), 0 );
        String s = "aa<>bb<cc>vv<<gg>>h<iii<jjj>kkk<<lll>mmm>ooo>ppp";
        System.out.println(
                String.format( "pat.matcher(\"%s\").replaceAll(\"@\") => %s",
                               s, pat.matcher( s ).replaceAll( "@" ) ) );
    }


}

Который при запуске дает:

{l0} = [^<>]*
{l1} = <[^<>]*>
{l2} = <[^<>]*(<[^<>]*>[^<>]*)*>
{l3} = <[^<>]*(<[^<>]*(<[^<>]*>[^<>]*)*>[^<>]*)*>
{l4} = <[^<>]*(<[^<>]*(<[^<>]*(<[^<>]*>[^<>]*)*>[^<>]*)*>[^<>]*)*>
pat.matcher("aa<>bb<cc>vv<<gg>>h<iii<jjj>kkk<<lll>mmm>ooo>ppp").replaceAll("@") => aa@bb@vv@h@ppp

Основное преимущество использования регулярных выражений заключается в том, что после того, как вы их написали, оно компилируется во внутреннее представление, которое должно посещать каждый символ строки, с которой сопоставляются один раз, что приводит к очень эффективному коду окончательного сопоставления (вероятно, вы не получите так эффективно писать код самостоятельно)

Sed

Для sed вам нужно только сгенерировать достаточно глубокое регулярное выражение и использовать его для разбора вашего текстового файла:

sed 's/<[^<>]*\(<[^<>]*\(<[^<>]*\(<[^<>]*\(<[^<>]*\(<[^<>]*>[^<>]*\)*>[^<>]*\)*>[^<>]*\)*>[^<>]*\)*>[^<>]*\)*>//g' file1.xml

Даст вам подходящие результаты (это 6 уровней вложенности или меньше - помните, ( и ) необходимо экранировать, чтобы они считались разделителями группы в sed)

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

l0="[^<>]*"
l1="<${l0}>"
l2="<${l0}\(${l1}${l0}\)*>"
l3="<${l0}\(${l2}${l0}\)*>"
l4="<${l0}\(${l3}${l0}\)*>"
l5="<${l0}\(${l4}${l0}\)*>"
l6="<${l0}\(${l5}${l0}\)*>"
echo regexp is "${l6}"
regexp is <[^<>]*\(<[^<>]*\(<[^<>]*\(<[^<>]*\(<[^<>]*\(<[^<>]*>[^<>]*\)*>[^<>]*\)*>[^<>]*\)*>[^<>]*\)*>[^<>]*\)*>
sed -e "s/${l6}/@/g" <<EOF
aa<>bb<cc>vv<<gg>>h<iii<jj<>j>k<k>k<<lll>mmm>ooo>ppp
EOF
aa@bb@vv@h@ppp

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

2
Luis Colorado 7 Май 2020 в 11:02