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



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

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

我可以用做到这一点

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

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

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

我得到

aa<>bbvv>h

而不是

aabbvvh

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

问题是,一旦允许嵌套<>字符,就可以从"常规的">";上下文无关">

正则语言是那些由正则表达式匹配的语言,而上下文无关语法通常不能由正则表达式解析。嵌套的无界级别阻碍了这一点,需要一个基于堆的自动机才能解析这些语言。

但有一个稍微复杂的解决方法,如果你认为在你面对的文本中允许的嵌套级别有上限,那么你可以将一种不允许的语言转换为正则语言,前提是非正则情况永远不会发生:

假设你的模式中永远不会有超过三个级别的嵌套,(这让你可以看到模式,并能够将其扩展到N个级别)你可以使用以下算法来构建一个正则表达式,它将允许你匹配三个层次的嵌套,但没有更多(您可以制作一个regexp来解析N个级别,但没有更多,这是regexp的无边界有边界性质:)。

让我们从下往上递归地构造表达式。如果只有一个嵌套级别,则只有<>,并且在内部找不到这两个级别(如果允许<,则允许更多嵌套级别,这在级别0是禁止的):

{l0} = [^<>]*

不包括CCD_ 13和CCD_。

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

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

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

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

现在,您可以通过在一对括号中交替排列{l0}{l2}来构建第三个。。。(请记住,{l-i}代表一个regexp,它允许多达i级别或更低级别的嵌套)

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

依此类推,依次形成序列

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

当您认为输入文件中不会有更深层次的嵌套时,请停止。

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

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

您可以看到regexp随着级别的增加而增长。好的是,你可以考虑最多三到四个级别,大多数文本都适合这个大教堂。

请参阅演示。

注释

尽管正则表达式看起来有些复杂,但不要犹豫构建它。认为您可以在程序中构建,只需使用我用来构建它的技术(例如,对于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,您只需要生成一个足够深的regexp,并使用它来解析您的文本文件:

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

会给你合适的结果(这是6层或更少的嵌套——记住()必须转义才能被视为sed中的组分隔符)

您的regexp可以通过以下方法使用shell变量构建:

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

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

您可以使用

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

最新更新