从文本文件中提取结构化数据(awk?):缺少的字段必须获得默认值
Posted
技术标签:
【中文标题】从文本文件中提取结构化数据(awk?):缺少的字段必须获得默认值【英文标题】:extract structured data from text files (awk ?) : missing fields must get default value 【发布时间】:2022-01-19 17:56:19 【问题描述】:(我正在使用 macos)。
我在子文件夹中有 70k 文本文件,我想以递归方式从中提取一些数据,然后 - 如果可能的话 - 将输出写入一个制表符分隔的文件以供以后电子表格处理。 来自我的 wiki 的文件(我使用 PmWiki,它将数据保存在 text files 中)在完成时以这种方式格式化(为了便于阅读,删除了不需要的数据):
version=
agent=
author=
charset=
csum=
ctime=1041379201
description=
host=
name=Name.12
rev=3
targets=Target.1,OtherTarget.23,Target.90
text=
time=
title=My title
author:
csum:
diff:
host:
author:
csum:
diff:
我想为名为 ctime name rev targets title
的字段(5 个字段)提取以 =
分隔的数据。
我的主要问题是如何获取数据(键 ctime= rev= targets= name= title=
),以及在某些缺失时使用默认值?
我认为必须测试每个目标键是否存在;如果缺少,则使用默认值创建它;然后提取想要的字段值,最后将数据制成表格。
预期的输出将是制表符分隔的;丢失的数据将被命名为以后容易捕获的东西。 即,对于示例中给出的完整文件(用制表符代替空格),输出将给出类似 (ctime, rev, name, title, targets) 的内容:
1041379201 3 Name.12 my title Target.1,OtherTarget.23,Target.90
并且,对于不完整的文件(第 1 行中缺少的字段是 rev ;在第 2 行中,rev 和标题):
1041379201 XXX Name.12 my title Target.1,OtherTarget.23,Target.90
1041379201 XXX Name.12 XXX Target.1,OtherTarget.23,Target.90
最终的项目是能够每月提取一次数据,然后在电子表格中易于使用的文本文件,每月更新。
我不太糟糕的尝试是这样的(但根本不起作用,缺少 if/else 条件):
awk 'BEGIN FS = "=" ; /^ctime=/
print $2
next
/^rev=/
print $2
next
/^name=/
print $2
next
/^title=/
print $2
next
/^targets=/
print $2
next'
这是一个原始的 PmWiki 文件(在这种情况下,我仍然想提取 ctime name rev targets title
(并为缺少的字段设置默认值,ctime
和 title
):
version=pmwiki-2.2.64 ordered=1 urlencoded=1
author=simon
charset=UTF-8
csum=add summary
name=Main.HomePage
rev=203
targets=PmWiki.DocumentationIndex,PmWiki.InitialSetupTasks,PmWiki.BasicEditing,Main.WikiSandbox
text=(:Summary:The default home page for the PmWiki distribution:)%0aWelcome to PmWiki!%0a%0aA local copy of PmWiki's%0adocumentation has been installed along with the software,%0aand is available via the [[PmWiki/documentation index]]. %0a%0aTo continue setting up PmWiki, see [[PmWiki/initial setup tasks]].%0a%0aThe [[PmWiki/basic editing]] page describes how to create pages%0ain PmWiki. You can practice editing in the [[wiki sandbox]].%0a%0aMore information about PmWiki is available from [[http://www.pmwiki.org]].%0a
time=1400472661
更新我的问题。
我发布问题的方式可能看起来比实际复杂。 由此,在 70k 文本文件中重复:
word1=line1
word2=line2
word3=line3
...
我想获取一个文件,收集每个line1, line3, lineX
(用于针对 word1、word2、wordX 的命令)并且在 word1=line1 或 word2=line2 或 wordX=lineX 根本不存在的情况下具有默认值。
最后,通过 Rick Smith 对Retrieve default value with grep -e?的回答,我发现了一些非常接近我需要的东西
【问题讨论】:
每个文件是否有固定的字段要求,例如每个文件总是有 21 个关键字段,例如“版本”或“代理”,或者每个文件是否出现多次?字段分隔符是“=”还是“:”?您的 2 行示例中的字段键在哪里? 关于by the way I don't know how to run this kind of command recursively, for current folder and subfolders
单独问一个问题,如果你无法弄清楚,一次只问一个问题。
编辑我的问题以便更好地理解:我愿意只收集每个文件中的几个字段(ctime=、rev=、name=、title=、targets=),并且需要有默认值缺失字段的值(在五个字段中)。对于这些字段,分隔符始终为 =
每个输入文件都被折叠成一行输出,对吧?您是否需要使用匹配的输出数据维护输入文件名,如果答案是“是”,您是否可以更新问题以使用(输入)文件名显示预期输出?
我不需要标题,也不需要文件名;我有文件,每个文件都由结构化的行列表组成:a=line1、b=line2、c=line3 等,我想在其中提取 line2、line3(我可以用awk 'print $2' FS='=')
做到这一点,但我不这样做'不知道该怎么做是为缺少 b=line2 的文件设置 linedefaut 值。
【参考方案1】:
我刚刚注意到您说您只想打印特定标签的值,这会使事情变得更容易。将 GNU awk 用于 ENDFILE
和 gensub()
:
$ cat tst.awk
BEGIN
OFS="\t"
numTags = split("ctime rev targets name title",tags)
for (tagNr=1; tagNr<=numTags; tagNr++)
tag = tags[tagNr]
printf "%s%s", tag, (tagNr<numTags ? OFS : ORS)
match($0,/^([[:alnum:]_]+)[=:](.*)/,a)
tag = a[1]
val = gensub(" ?" OFS " ?"," ","g",a[2])
tag2val[tag] = val
ENDFILE
for (tagNr=1; tagNr<=numTags; tagNr++)
tag = tags[tagNr]
val = ( tag in tag2val ? tag2val[tag] : "_ABSENT_" )
val = ( val == "" ? "_NULL_" : val )
printf "%s%s", val, (tagNr<numTags ? OFS : ORS)
delete tag2val
$ awk -f tst.awk file
ctime rev targets name title
1041379201 3 Target.1,OtherTarget.23,Target.90 Name.12 My title
$ awk -f tst.awk file | column -s$'\t' -t
ctime rev targets name title
1041379201 3 Target.1,OtherTarget.23,Target.90 Name.12 My title
原答案:
如果每个输入文件中的标签都是唯一的,这听起来可能就是您正在尝试做的事情,需要 GNU awk 进行多个扩展:
$ cat tst.awk
BEGIN OFS="\t"
match($0,/^([[:alnum:]_]+)[=:](.*)/,a)
tag = a[1]
val = gensub(" ?" OFS " ?"," ","g",a[2])
if ( !seen[tag]++ )
tags[++numTags] = tag
key2val[ARGIND,tag] = val
END
for (tagNr=1; tagNr<=numTags; tagNr++)
tag = tags[tagNr]
printf "%s%s", tag, (tagNr<numTags ? OFS : ORS)
for ( fileNr=1; fileNr<=ARGIND; fileNr++)
for (tagNr=1; tagNr<=numTags; tagNr++)
tag = tags[tagNr]
key = fileNr SUBSEP tag
val = ( key in key2val ? key2val[key] : "_ABSENT_" )
val = ( val == "" ? "_NULL_" : val )
printf "%s%s", val, (tagNr<numTags ? OFS : ORS)
$ awk -f tst.awk file
version agent author charset csum ctime description host name rev targets text time title diff
_NULL_ _NULL_ _NULL_ _NULL_ _NULL_ 1041379201 _NULL_ _NULL_ Name.12 3 Target.1,OtherTarget.23,Target.90 _NULL_ _NULL_ My title _NULL_
查看视觉对齐的列:
$ awk -f tst.awk file | column -s$'\t' -t
version agent author charset csum ctime description host name rev targets text time title diff
_NULL_ _NULL_ _NULL_ _NULL_ _NULL_ 1041379201 _NULL_ _NULL_ Name.12 3 Target.1,OtherTarget.23,Target.90 _NULL_ _NULL_ My title _NULL_
只需在所有文件上一次运行它:
awk -f tst.awk file1 file2 etc.
它会找出所有文件中的所有标签,然后打印一个 TSV,其中包含所有这些文件中所有这些标签的值。
【讨论】:
我忘了说我正在使用 macos ;使提议的 Ed 的脚本失败:>>> match($0,/^([[:alnum:]_]+)[=:](.*)/, 然后安装gawk,很简单(google一下)。 Gawk 已安装(当您知道不该做什么时很容易)。您的脚本经过测试,可以工作,但必须针对某些源文件进行改进(输出可能无法正常工作,具体取决于来源:我怀疑正则表达式不完全是我需要的)。我找到了另一种 awk 解决方案,它看起来与我的项目更兼容。 老实说 - 很抱歉,但对于您正在尝试做的事情,几乎不可能有比这更好的解决方案。您在问题中引用的当然不是因为它会慢几个数量级,以随机顺序产生输出,无法区分空值和缺失标签,如果您的输入包含选项卡或"
s,则会失败等。如果我使用的正则表达式(我根据您的示例创建)不是您需要的,则修复它,或者如果您可以在其他答案中使用$1
,那么只需像您一样设置 FS并使用$1
而不是a[1]
等。【参考方案2】:
假设:
输入文件至少有一行field=value
条目不跨越多行(即,field
和 value
均不包含嵌入式换行符/回车符)
field
和 value
均不包含 =
字符(即,=
在每个输入行仅显示一次)
OP 可以创建一个包含所需字段列表及其默认值的新文件 [这消除了对字段名称、它们的顺序和它们的默认值进行硬编码的需要]
示例输入文件:
$ cat 1.txt
version=
agent=
author=
charset=
csum=
ctime=1041379201
description=
host=
name=Name.12
rev=3
targets=Target.1,OtherTarget.23,Target.90
text=
time=
title=My title
author:
csum:
diff:
host:
author:
csum:
diff:
$ cat 2.txt
version=pmwiki-2.2.64 ordered=1 urlencoded=1
author=simon
charset=UTF-8
csum=add summary
name=Main.HomePage
rev=203
targets=PmWiki.DocumentationIndex,PmWiki.InitialSetupTasks,PmWiki.BasicEditing,Main.WikiSandbox
text=(:Summary:The default home page for the PmWiki distribution:)%0aWelcome to PmWiki!%0a%0aA local copy of PmWiki's%0adocumentation has been installed along with the software,%0aand is available via the [[PmWiki/documentation index]]. %0a%0aTo continue setting up PmWiki, see [[PmWiki/initial setup tasks]].%0a%0aThe [[PmWiki/basic editing]] page describes how to create pages%0ain PmWiki. You can practice editing in the [[wiki sandbox]].%0a%0aMore information about PmWiki is available from [[http://www.pmwiki.org]].%0a
time=1400472661
$ cat 3.txt # NOTE: no matches with fields in defaults.txt
other=abc
line=def
假设 OP 可以创建一个包含所需字段名称和默认值的文件,例如:
$ cat defaults.txt
ctime=CCCC
name=NNNN
rev=REV
targets=NO_TARGETS
title='BLANK TITLE'
注意:最终输出中的字段顺序与defaults.txt
中的字段顺序相同
一个awk
想法:
awk -F'=' '
function print_line()
pfx=""
if ( printme ) # skip the first call to this function
for ( i=1; i<=ordno; i++ ) # loop through our list of desired fields ...
printf "%s%s", pfx, ( order[i] in fields ? fields[order[i]] : defaults[order[i]] )
pfx=OFS
print "" # terminate line
delete fields # reset our fields[] array
printme=1 # enable printing of fields[] contents on next call
BEGIN OFS="\t" # output field delimiter
printme=0 # disable printing of fields[] on first function call
FNR==NR # process 1st file, ie, our desired fields and their associated default values
order[++ordno]=$1 # save order of fields
defaults[$1]=$2 # save default values
next
FNR==1 print_line() # upon seeing a new file flush the contents of fields[] to stdout
print "#### "FILENAME # remove this line once OP validates output
$1 in defaults fields[$1]=$2 # if field #1 is in our default[] array then save field #2 in our fields[] array
END print_line() # flush last file/fields[] to stdout
' defaults.txt 1.txt 2.txt 3.txt
注意:我无权访问 MacOS/awk 安装,因此 OP 需要确定这是否适用于他们的环境
这会生成:
#### 1.txt
1041379201 Name.12 3 Target.1,OtherTarget.23,Target.90 My title
#### 2.txt
CCCC Main.HomePage 203 PmWiki.DocumentationIndex,PmWiki.InitialSetupTasks,PmWiki.BasicEditing,Main.WikiSandbox 'BLANK TITLE'
#### 3.txt
CCCC NNNN REV NO_TARGETS 'BLANK TITLE'
没有print "#### "FILENAME
:
1041379201 Name.12 3 Target.1,OtherTarget.23,Target.90 My title
CCCC Main.HomePage 203 PmWiki.DocumentationIndex,PmWiki.InitialSetupTasks,PmWiki.BasicEditing,Main.WikiSandbox 'BLANK TITLE'
CCCC NNNN REV NO_TARGETS 'BLANK TITLE'
【讨论】:
脚本测试;确实可以很好地进行演示(我希望打印现有的 ctime,我得到默认值 CCCC ;如果 rev 不存在,标题在目标值中间被融化(叠加)+名称被叠加的默认值删除修订版)。 我的错误:至于演示文稿,以 txt 文件发送的输出确实有效(终端破坏了布局,抱歉)。 ctime 确实像预期的那样工作,当 defaults.txt 被更正(错别字)时:ctime 代替 cctime。以上是关于从文本文件中提取结构化数据(awk?):缺少的字段必须获得默认值的主要内容,如果未能解决你的问题,请参考以下文章