我想根据文本内容的缩进深度来标记文本内容(使用类似 XML 的标记)。应保留空行。下面的示例内容缩进 1、2 或 3 个制表符。
输入
aaa bbb bbb aaa ccc ccc bbb bbb
我想将相同缩进级别的行分组,并将这些缩进级别转换为标签x
、y
和z
,如下所示:
输出
<x>aaa</x>
<y>bbb
bbb</y>
<x>aaa</x>
<z>ccc
ccc</z>
<y>bbb
bbb</y>
我怎样才能做到这一点?
答案1
问题描述:
输入文件包含文本,以零个或多个制表符缩进。具体来说,输入中的每一行都是以下之一:
- 空白,或
- 零个或多个制表符(最多有限制;见下文),后跟一个既不是空格也不是制表符的字符(后跟零个或多个任意字符)。
有不行
- 以零个或多个制表符开头,后跟一个空格。(这意味着没有以空格开头的行。)
或 - 完全由一个或多个标签组成(没有其他内容)。
或 - 从超过指定数量的标签开始。
输入应按逻辑分解为多组行,这些行要么
- 空白,或
- 缩进相同数量的制表符。
空行应不加修改地传递到输出。
应指定标签列表;例如,x
,y
和zz
。一组(非空白)行如果缩进的标签为零(即不缩进),则应使用 和 括起来<x>
。</x>
一组缩进一个标签的行应使用 和 括起来<y>
。</y>
一组缩进两个标签的行应使用 和 括起来<zz>
。</zz>
(行不会使用超过两个标签进行缩进。)
一组(非空白行)的第一行应在制表符和文本之间插入开始标记。一组的最后一行应在文本末尾附加结束标记。一个组可能由一行组成,因此第一行也可能是最后一行。除第一行之外,组中所有行都应额外缩进(在制表符和文本之间插入空格),缩进宽度与开始标记相同。
例如(使用 ―→
表示选项卡),这个输入:
aaa
―→ Once upon a midnight dreary,
―→ while I pondered, weak and weary,
Quoth the Raven, “Nevermore.”
―→ ―→ The quick brown fox
―→ ―→ jumps over the lazy dog.
―→ It was a dark and stormy night.
―→ Suddenly a shot rang out.
应翻译成输出:
<x>aaa</x>
―→ <y>Once upon a midnight dreary,
―→ while I pondered, weak and weary,</y>
<x>Quoth the Raven, “Nevermore.”</x>
―→ ―→ <zz>The quick brown fox
―→ ―→ jumps over the lazy dog.</zz>
―→ <y>It was a dark and stormy night.
―→ Suddenly a shot rang out.</y>
解决方案:
显然,在读完下一行之前,我们并不知道该如何处理输入的一行。解决这个问题的方法是保存上一行的内容,等读完下一行后再处理。
因此,如下所示:
awk '
BEGIN {
num_tags = split("x y zz", tags)
for (i=1; i<=num_tags; i++)
{
len = length(tags[i]) + 2
tag_pad[i] = ""
for (j=1; j<=len; j++) tag_pad[i] = tag_pad[i] " "
}
}
{
if (NF == 0)
indent_num = 0
else
{
indent_num = index($0, $1)
indent_str = substr($0, 1, indent_num-1)
restOfLine = substr($0, indent_num)
}
if (indent_num != saved_indent_num && saved != "")
{
print saved "</" tags[saved_indent_num] ">"
saved = ""
}
if (NF == 0)
print
else if (indent_num > num_tags)
{
errmsg = "Error: line %d has an indent level of %d.\n"
printf errmsg, NR, indent_num > "/dev/stderr"
exit 1
}
else if (indent_num == saved_indent_num)
{
print saved
saved = indent_str tag_pad[indent_num] restOfLine
}
else
saved = indent_str "<" tags[indent_num] ">" restOfLine
saved_indent_num = indent_num
}
END {
if (saved != "")
print saved "</" tags[saved_indent_num] ">"
}
'
BEGIN 块通过拆分空格分隔的字符串来初始化标签(x
、y
和)。数组包含足够的空格以匹配标签的宽度(包括和): 和是三个空格;是四个空格。zz
tag_pad
<
>
tag_pad[1]
tag_pad[2]
tag_pad[3]
读取一行输入后,我们会对其进行解析。如果它没有字段(NF == 0
),则它必须是空白的(因为我们已经指定没有一行完全由空格和制表符组成),因此设置为 0。否则,通过查找(整个行) 中(第一个单词)indent_num
的位置来测量缩进。返回一个从 1 开始的值,因此这实际上比第一个非空白字符之前的空白字符数多一个(请记住,我们假设这些都是制表符)。这很幸运,因为现在 对应于和数组中的条目。然后我们将该行分成(空白)和(缩进后的所有内容)。$1
$0
index
indent_num
tags
tag_pad
indent_str
restOfLine
现在我们依赖已保存的信息。如果此行的缩进与前一行不同,则我们将开始一个新组。如果有是保存的行,将其写出,并在行末添加适当的结束标记。
如果当前行为空行,则直接打印。检查当前缩进级别是否过高,如果过高则放弃。如果当前缩进与上一个缩进相同,则这是已开始组的续行,因此只需打印已保存的(上一个)行,并使用以下命令构建一个作为saved
当前行的新字符串宽度当前标签插入在缩进和文本之间。否则,我们将开始一个新组,因此构建一个saved
字符串,该字符串是当前行,起始标签(本身)插入在缩进和文本之间。
当我们到达输入的末尾时,像之前一样结束当前组。