删除<和>之间的子字符串(包括括号),里面没有尖括号

时间:2020-05-06 08:23:51

标签: regex linux sed

我必须使用sed命令修改类似html的文本。我必须删除从一个或多个<字符开始的子字符串,然后删除0个或多个出现的除尖括号之外的任何字符,然后删除1个或多个>字符。

例如:来自 aaa<bbb>ccc我想得到aaaccc

我能够通过

做到这一点

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

,但如果<>个字符之间为空字符串,或者文本中有两个<<>>,则此命令不起作用。 例如,来自

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

我知道

aa<>bbvv>h

代替

aabbvvh

如何修改它以得到正确的结果?

2 个答案:

答案 0 :(得分:2)

问题是,一旦允许嵌套<>字符,就将语言类型从“常规” 转换为“无上下文” < / em>。

正则语言是与正则表达式匹配的语言,而上下文无关的语法通常无法通过正则表达式进行解析。嵌套的 unbounded 级别阻止了这一点,需要基于堆的自动机才能解析此类语言。

但是有一些复杂的解决方法,如果您认为在您所面对的文本中允许的嵌套级别有上限,那么您可以根据前提是非常规情况永远不会发生:

让我们假设您在模式中嵌套的层次永远不会超过三个,(这使您可以看到模式并将其扩展到N个层次),可以使用以下算法来构建一个正则表达式,允许您匹配三个嵌套级别,但不能更多(您可以创建一个正则表达式来解析N个级别,但不能更多,这就是正则表达式的 umbounded bounded 性质: ))。

让我们从下至上递归构造表达式。仅使用一个嵌套级别,您只有<>,并且在内部都找不到它们(如果您允许<,则允许更多嵌套级别,这在级别0处被禁止):

{l0} = [^<>]*

不包含<>字符的字符串。

您的匹配文本将属于此类字符串,并由一对<>字符包围:

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

现在,您可以通过交替{l0}{l1}{l0}{l1}...{l0}(即{l0}({l1}{l0})*并用<>包围整个事物来构建第二层嵌套,以构建{ {1}}

{l2}

现在,您可以通过在一对方括号中交替排列{l2} = <{l0}({l1}{l0})*> = <[^<>]*(<[^<>]*>[^<>]*)*> {l0}来构建第三个……(请记住,{l2}代表一个正则表达式,最多允许{{ 1}}或更少的嵌套级别

{l-i}

依次类推,您形成一个序列

i

在您认为输入文件中没有更深的嵌套时停止。

所以您的三级正则表达式是:

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

您会看到正则表达式随着您考虑更多级别而增长。好消息是,您最多可以考虑三级或四级,并且大多数文本都适合该类别。

请参见demo

注意

尽管正则表达式看起来有些复杂,但不要犹豫。认为您可以在程序内部构建,只需使用我用来构建它的技术即可(例如,对于16级嵌套正则表达式,您将得到一个很大的字符串,很难编写它手工制作,但是很容易用计算机构建)

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

在运行时给出:

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

使用正则表达式的主要优点是,一旦编写了正则表达式,它就会编译成一个内部表示形式,该形式只需要访问一次要匹配的字符串的每个字符,从而产生非常有效的最终匹配代码(可能是不能自己编写代码那么高效)

Sed

对于sed,您只需要生成足够深的正则表达式,然后使用它来解析您的文本文件即可:

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( "@" ) ) );
    }


}

将为您提供适当的结果(这是6个嵌套级别或更少的嵌套-请记住,{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 's/<[^<>]*\(<[^<>]*\(<[^<>]*\(<[^<>]*\(<[^<>]*\(<[^<>]*>[^<>]*\)*>[^<>]*\)*>[^<>]*\)*>[^<>]*\)*>[^<>]*\)*>//g' file1.xml 必须转义为(中的组定界符)

可以使用shell变量通过以下方法构造您的正则表达式:

)

(我使用sed作为替换模式,因此您可以看到在输入字符串中检测到了模式)

答案 1 :(得分:1)

您可以使用

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

模式匹配

  • <\+-与<\{1,\}之外的0个或多个字符匹配的否定括号表达式

请注意,在最后的POSIX ERE中,未转义的<是匹配1个或多个匹配项的量词,与POSIX BRE模式中的[^>]*相同。

请参见online sed demo

>

每个sed命令的结果为>\+