我需要在命令行中解析大量文本,并将所有(可能是嵌套的)文本引号替换为空格。引号用特定语法标记:[quote=username]quoted text[/quote]
。
带有嵌套引号的示例输入可能类似于:
text part 1 [quote=foo] outer quote 1 [quote=bar] inner quote [/quote] outer quote 2 [/quote] text part 2 [quote=foo-bar] next quote [/quote] text part 3
预期输出为:
text part 1 text part 2 text part 3
在...的帮助下这个问题我以某种方式得到了它的工作(上面得到了输出),sed ':b; s/\[quote=[^]]*\][^[\/]*\[\/quote\]/ /g; t b'
但中间部分([^[\/]
] 是有问题的,因为引号可以包含类似[
或 的字符]
。
话虽这么说,sed
如果输入是例如,我的命令不起作用。
text part 1 [quote=foo] outer quote 1 [quote=bar] inner quote [foo] [/quote] outer quote 2 [/quote] text part 2 [quote=foo-bar] next quote [/quote] text part 3
一个问题是它sed
似乎不支持非贪婪限定符,因此总是从输入中捕获最长的可能匹配。这让人很难应对A)用户名和b)一般引用的文本。
我还猜想这sed
不是解决这个问题的最佳工具,它甚至可能无法做这样的事情。也许例如。perl
或者awk
可以工作得更好吗?
现在最后一个问题是解决这个问题的最佳和最有效的方法是什么?
答案1
如果您知道输入不包含<
或>
字符,您可以这样做:
sed '
# replace opening quote with <
s|\[quote=[^]]*\]|<|g
# and closing quotes with >
s|\[/quote\]|>|g
:1
# work our way from the inner quotes
s|<[^<>]*>||g
t1'
如果它可能包含<
或>
字符,您可以使用如下方案转义它们:
sed '
# escape < and > (and the escaping character _ itself)
s/_/_u/g; s/</_l/g; s/>/_r/g
<code-above>
# undo escaping after the work has been done
s/_r/>/g; s/_l/</g; s/_u/_/g'
与perl
, 使用递归正则表达式:
perl -pe 's@(\[quote=[^\]]*\](?:(?1)|.)*?\[/quote\])@@g'
或者甚至,正如您提到的:
perl -pe 's@(\[quote=.*?\](?:(?1)|.)*?\[/quote\])@@g'
使用perl
,您可以通过添加选项来处理多行输入-0777
。对于sed
,您需要在代码前添加以下前缀:
:0
$!{
N;b0
}
从而将整个输入加载到模式空间中。
答案2
我检查了这个,它对我有用。您可能想选择另一个临时模式而不是foobar
.如果没有它,sed
就会删除标签之间的所有内容,只留下text part 1 text part 3
sed -e 's/\/quote\]/foobar\]/3' -e 's/\[.*\/quote\]//' -e 's/\[.*foobar]//' testfile
相反,如果testfile
你可以用管道来代替cat
答案3
一个小脚本,在每个起始引号上递增计数器变量,并在每个结束引号上递减计数器变量。如果计数器变量更大0
,则跳过文本片段。
#!/bin/bash
# disable pathname expansion
set -f
cnt=0
for i in $(<$1); do
# start quote
if [ "${i##[quote=}" != "$i" ] && [ "${i: -1}" = "]" ]; then
((++cnt))
elif [ "$i" = "[/quote]" ]; then
((--cnt))
elif [ $cnt -eq 0 ]; then
echo -n "$i "
fi
done
echo
输出:
$ cat q1
text part 1 [quote=foo] outer quote 1 [quote=bar] inner quote [/quote] outer quote 2 [/quote] text part 2 [quote=foo-bar] next quote [/quote] text part 3
$ ./parse.sh q1
text part 1 text part 2 text part 3
$ cat q2
text part 1 [quote=foo] outer quote 1 [quote=bar] inner quote [foo] [/quote] outer quote 2 [/quote] text part 2 [quote=foo-bar] next quote [/quote] text part 3
$ ./parse.sh q2
text part 1 text part 2 text part 3
答案4
您可以按照POSIX sed
此处的详细说明执行此操作。请注意,此解决方案适用于您显示的两种输入。输入的限制不是多行,因为我们使用换行符作为标记来实现所需的转换。
$ sed -e '
:top
/\[\/quote]/!b
s//\
&/
s/\[quote=/\
\
&/
:loop
s/\(\n\n\)\(\[quote=.*\)\(\[quote=.*\n\)/\2\1\3/
tloop
s/\n\n.*\n\[\/quote]//
btop
' input.txt