我必须使用sed命令修改类似html的文本。我必须删除从一个或多个<
字符开始的子字符串,然后删除0个或多个出现的除尖括号之外的任何字符,然后删除1个或多个>
字符。
例如:来自
aaa<bbb>ccc
我想得到aaaccc
我能够通过
做到这一点 "s/<[^>]\+>//g"
,但如果<>
个字符之间为空字符串,或者文本中有两个<<>>
,则此命令不起作用。
例如,来自
aa<>bb<cc>vv<<gg>>h
我知道
aa<>bbvv>h
代替
aabbvvh
如何修改它以得到正确的结果?
答案 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,您只需要生成足够深的正则表达式,然后使用它来解析您的文本文件即可:
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命令的结果为>\+
。