使用 awk sed 等。从没有结束标签的文件中解析字段
Posted
技术标签:
【中文标题】使用 awk sed 等。从没有结束标签的文件中解析字段【英文标题】:Using awk sed et al. to parse fields from a file having no end tags 【发布时间】:2020-04-05 08:34:30 【问题描述】:我想要的输出是一个逗号分隔的文件。如果一切都失败了,我知道我可以使用 for/each 逻辑或其他东西编写脚本,但我希望使用 awk 和 sed 找到一个优雅的解决方案,这在我之前曾多次为我服务过。我只是被这个难住了。
下面是数据的表示,后面是期望的结果。注意:每个 GROUPNUMBER 都有一组关联的 GROUPMEMBER、GROUPMEMBERID 和 MEMBERRANK 字段(它们并不总是像在示例数据中那样位于行的第一个字符)。 GROUPNUMBER 周围的其他行和文本不重要,也可能有冒号,并且在此处表示为 lorem ipsum 文本。一些 GROUPNUMBER 根本没有关联的字段(如最后几行的示例数据所示,对于 Group#88888)。此外,在示例数据中,GROUPNUMBER 部分显示为用空行分隔,但情况并非总是如此(有时下一部分与前一部分没有换行符)。
样本数据:
loremipsum: loremipsum?# loremipsum/123: loremipsumxx GROUPNUMBER:111222
loremipsum123:loremispum loremipsumxxxx
GROUPMEMBER:Joe:MEMBERRANK:1
GROUPMEMBERID:1234
GROUPMEMBER:Mike:MEMBERRANK:1
GROUPMEMBERID:2234
loremipsum14e3:loremispum loremipsumxxxx
loremipsum1eer534:loremispum loremipsumxxfgt
GROUPMEMBER:Sue:MEMBERRANK:89
GROUPMEMBERID:3234
GROUPMEMBER:John:MEMBERRANK:323
GROUPMEMBERID:4234:loremipsumaaa_loremipsum
loremipsum: loremipsum..<?# loremipsum/123: loremipsumxx GROUPNUMBER:333444
loremipsum123:loremispum loremipsumxxxx
GROUPMEMBER:Frank:MEMBERRANK:4
GROUPMEMBERID:5234
GROUPMEMBER:Laurie:MEMBERRANK:4
GROUPMEMBERID:6234
loremipsum: loremipsum..<?# loremipsum/123: loremipsumxx GROUPNUMBER:88888
loremipsum123:loremispum loremipsumxxxx
期望的输出:
GROUPNUMBER, MEMBERNAME, MEMBERID, MEMBERRANK
Example from above data:
111222,Joe,1234,1
111222,Mike,2234,1
111222,Sue,3234,89
111222,John,4234,323
333444,Frank,5234,4
333444,Laurie,6234,4
88888,,,
【问题讨论】:
which have served me well so many times before
在这个论坛上,我们鼓励其他人发布他们尝试过的内容。请张贴您尝试过的内容,并说明是什么阻止了您编写它。你的问题到底是什么?虽然我相信它可以在 sed 中完成,但 awk 解决方案会更容易且更具可读性。
edit 你的问题是解释如何从你 loremipsum 编辑的所有其余文本中识别GROUPNUMBER:111222
等字符串。例如,您输入中唯一的字符串是all upper case then colon then digits
?
<input sed 's/GROUPNUMBER:/\n&/g;s/GROUPMEMBER:/\n&/g;s/GROUPMEMBERID:/\n&/g;s/MEMBERRANK/\n&/g' | awk -v OFS=, -F: 'function o(v)if(v)print n,m,i,r;p=m=i=r=""$1=="GROUPNUMBER"o(p);n=$2;p=1$1=="GROUPMEMBER"o(m);m=$2$1=="GROUPMEMBERID"o(i);i=$2$1=="MEMBERRANK"o(r);r=$2n&&m&&i&&ro(1)ENDo(p)'
【参考方案1】:
在 GNU sed 中可能几乎不可能......但最好使用带有哈希表的东西,或者至少比保留空间更多的变量:
sed -nE '/GROUPNUMBER:/s/.*://;h;/GROUPMEMBER:/N;G;s/GROUPMEMBER:(.*):MEMBERRANK:(.*)\nGROUPMEMBERID:([^:]*).*\n(.*)/\4,\1,\2,\3/p;$g;s/$/,,,/p' file
这是对最后一组的粗俗处理...最后一行将始终是最后的组号和三个逗号。
要点:
sed -nE
仅在指定时打印,并允许无反斜杠捕获组
h
持有 GROUPNUMBER
N;G
在 GROUPMEMBER 行上附加后续行和组号
捕获组 1-4 重新排序以格式化输出
最后一行 $
的 hack,它使用 g
获取最后一个 GROUPNUMBER 并用三个逗号打印
【讨论】:
【参考方案2】:这不是特别容易,但也不是非常困难。所有有趣的信息都在由冒号分隔的字段中,因此一部分是让awk
将输入行拆分为基于冒号的字段(-F:
)。然后需要识别组号、组成员、成员等级和成员 ID。任何没有匹配信息的行都会被忽略。可以通过扫描字段来查找与关键字匹配的字段,然后将其后面的字段作为值返回来查找字段值。在下面的代码中,函数extractor
完成了这项工作。还需要跟踪打印组号的次数。在输入结束时,或者识别到新的组号时,如果旧的组号已经打印了零次,则需要打印组信息。函数print_member
打印一个成员;它节省了写出 printf
语句 3 次。
awk -F: '
function extractor(tag, i)
for (i = 1; i < NF; i++)
if ($i ~ tag)
return $(i + 1)
return ""
function print_member()
printf "%s,%s,%s,%s\n", groupnumber, groupmember, groupmemberid, memberrank
/GROUPNUMBER:[0-9]+/
if (groupnumber != "" && groupcount == 0)
print_member()
groupnumber = extractor("GROUPNUMBER")
groupmember = ""
memberrank = ""
groupmemberid = ""
groupcount = 0
/GROUPMEMBER:[^:]+:MEMBERRANK:[0-9]+/
groupmember = extractor("GROUPMEMBER")
memberrank = extractor("MEMBERRANK")
/GROUPMEMBERID:[0-9]+/
groupmemberid = extractor("GROUPMEMBERID")
print_member()
groupcount++
END
if (groupcount == 0)
print_member()
' data
给定问题中的数据文件(名称data
),输出为:
111222,Joe,1234,1
111222,Mike,2234,1
111222,Sue,3234,89
111222,John,4234,323
333444,Frank,5234,4
333444,Laurie,6234,4
88888,,,
这似乎是所需的输出。现在考虑一个修改过的输入文件(有许多添加),如下所示:
loremipsum: loremipsum?# loremipsum/123: loremipsumxx GROUPNUMBER:111222:hydrangea
loremipsum123:loremispum loremipsumxxxx
GROUPMEMBER:Joe:MEMBERRANK:1:orchid
GROUPMEMBERID:1234
GROUPMEMBER:Mike:piscatore:MEMBERRANK:1
GROUPMEMBERID:2234
loremipsum14e3:loremispum loremipsumxxxx
loremipsum1eer534:loremispum loremipsumxxfgt
GROUPMEMBER:Sue:MEMBERRANK:89
GROUPMEMBERID:3234
GROUPMEMBER:John:MEMBERRANK:323
GROUPMEMBERID:4234:loremipsumaaa_loremipsum
loremipsum: loremipsum..<?# loremipsum/123: loremipsumxx GROUPNUMBER:333444
loremipsum123:loremispum loremipsumxxxx
GROUPMEMBER:Frank:MEMBERRANK:4
GROUPMEMBERID:5234
GROUPMEMBER:Laurie:MEMBERRANK:4
GROUPMEMBERID:6234
loremipsum: loremipsum..<?# loremipsum/123: loremipsumxx GROUPNUMBER:88888
loremipsum123:loremispum loremipsumxxxx
loremipsum: loremipsum..<?# loremipsum/123: loremipsumxx GROUPNUMBER:222444
loremipsum123:loremispum loremipsumxxxx
GROUPMEMBER:Helen Mary Ann:MEMBERRANK:1
loremipsum: loremipsum..<?# loremipsum/123: loremipsumxx GROUPNUMBER:222555
loremipsum123:loremispum loremipsumxxxx
loremipsum123:loremispum loremipsumxxxx
现在的输出是:
111222,Joe,1234,1
111222,Joe,2234,1
111222,Sue,3234,89
111222,John,4234,323
333444,Frank,5234,4
333444,Laurie,6234,4
88888,,,
222444,Helen Mary Ann,,1
222555,,,
这看起来很合理。 Helen Mary Ann 名字中的空格无关紧要;她没有会员ID。中间没有信息的组也正确显示。
显然,您可以通过将其放入文件并将data
替换为"$@"
来将其转换为可用的shell 脚本,以便它处理命令行中给出的文件名,或者读取标准输入(如果有)没有这样的名字。
如 cmets 中所述,上面的代码假定 GROUPMEMBER 和 MEMBERRANK 字段在一行上是连续的,中间没有任何随机的“ipsum lorem”类型字段。如果实际上两者之间可能存在“ipsum lorem”字段(我在第二个数据集中使用了piscatore
),则需要修改脚本以分别识别 GROUPMEMBER 和 MEMBERRANK。这还有一个额外的好处,如果输入包含:
ipsum lorem:MEMBERRANK:1:ipsum lorem:GROUPMEMBER:Hailey:ipsum lorem
或
ipsum lorem:MEMBERRANK:110:ipsum lorem
hallucination:GROUPMEMBER:Julian:doldrums
然后它会准确地识别信息(在第二个示例中,行按任意顺序排列)。 GROUPMEMBERID 必须是具有给定 GROUPNUMBER 的每个成员的三个条目中的最后一个。
awk -F: '
function extractor(tag, i)
for (i = 1; i < NF; i++)
if ($i ~ tag)
return $(i + 1)
return ""
function print_member()
printf "%s,%s,%s,%s\n", groupnumber, groupmember, groupmemberid, memberrank
/GROUPNUMBER:[0-9]+/
if (groupnumber != "" && groupcount == 0)
print_member()
groupnumber = extractor("GROUPNUMBER")
groupmember = ""
memberrank = ""
groupmemberid = ""
groupcount = 0
/GROUPMEMBER:[^:]+/
groupmember = extractor("GROUPMEMBER")
/MEMBERRANK:[0-9]+/
memberrank = extractor("MEMBERRANK")
/GROUPMEMBERID:[0-9]+/
groupmemberid = extractor("GROUPMEMBERID")
print_member()
groupcount++
END
if (groupcount == 0)
print_member()
' data
现在的输出是:
111222,Joe,1234,1
111222,Mike,2234,1
111222,Sue,3234,89
111222,John,4234,323
333444,Frank,5234,4
333444,Laurie,6234,4
88888,,,
222444,Helen Mary Ann,,1
222555,,,
【讨论】:
如果 GROUPNUMBER 在一行中跟在 GROUPMEMBER、GROUPMEMBERID 或 MEMBERRANK 之后,则会中断 @jhnc — 如果示例中的数据不能合理地完全说明可能发生的情况,那么是的,处理可能需要更改。示例数据清楚地表明 GROUPNUMBER 只出现在一行上,没有任何其他项目。如果这在完整数据集上不准确,则需要在问题中显示。我们只需要解决问题显示的内容或可以推断的内容。 问题的最后一行提到部分可能会在没有换行符的情况下继续 @jhnc:由于前言说“在示例数据中,GROUPNUMBER 部分显示为用空行分隔,但情况并非总是如此”,我将其解释为空行(又名“换行符”)并不总是存在,如我的第二个数据文件所示。如果“换行符”真的是“换行符”——一种可能但不一定合理的解释——那么是的,需要做更多的工作。 YMMV。除非 OP 建议所指的换行符是“换行符”而不是“空白行”,否则我不会担心。 很难清楚地解释这一点,很抱歉造成混淆。每行后面都有一个换行符。我是说在示例数据中看到的额外空行(双换行符?)并不总是存在,有时它看起来像 Jonathan 的数据。我之所以提到这一点,是因为我不能依靠“双换行符”来表示 Group 的结束。使用函数有点像编写脚本,但是将它们放在单个 awk 命令中真是太酷了。迫不及待地想明天在工作中试试这个。不过有一件事,您最终输出的第二行显示“Joe”,但应该是“Mike”,这是不是打错字了?以上是关于使用 awk sed 等。从没有结束标签的文件中解析字段的主要内容,如果未能解决你的问题,请参考以下文章