Удалите подстроки между <и> (включая скобки) без угловых скобок внутри - PullRequest
1 голос
/ 06 мая 2020

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

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

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

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

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

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

я получаю

aa<>bbvv>h

вместо

aabbvvh

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

Ответы [ 2 ]

2 голосов
/ 07 мая 2020

Проблема в том, что как только вы разрешаете вложение символов < и >, вы конвертируете тип языка из "обычный" в "контекстно-свободный" .

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

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

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

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

{l0} = [^<>]*

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

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

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

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

{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)

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

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

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

1 голос
/ 06 мая 2020

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

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

Соответствие шаблонов

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

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

См. в Интернете sed demo :

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...