这是输入文件。
QSUM HEADER STOCK DATE TIME
206 CC-USER REJECT SENT TNPPP 200322 104914600
TI JPS TNN LTNN PP JP
8 6 0 1
AA NS CPOODE
4 899599991119
TYPE AI
12 18
QSUM HEADER STOCK DATE TIME
206 CC-USER REJECT SENT TNPPP 200322 115844000
TI JPS TNN LTNN PP JP
8 6 0 1
AA NS CPOODE
4 899599991555
TYPE AI
12 18
QSUM HEADER STOCK DATE TIME
103 SUITE FAIL, SUBTRACT FAILURE TPNRM 200318 031124100
TI PNC TNN PP JP AA
2 1499 177 123 1 4
NS FLAG
999999999999 ORIGIN
TI CPO
QSUM HEADER STOCK DATE TIME
103 SUITE FAIL, SUBTRACT FAILURE TPNRM 200318 031124200
TI PNC TNN PP JP AA
2 1499 177 123 1 4
NS FLAG
999999999999 ORIGIN
TI CPO
我需要如下输出
QSUM HEADER STOCK DATE TIME TI PNC JPS TNN LTNN PP JP AA NS FLAG OPCODE TYPE AI TI CPO
206 CC-USER REJECT SENT TNPPP 200322 104914600 8 6 0 1 4 899599991119 12 18
206 CC-USER REJECT SENT TNPPP 200322 115844000 8 6 0 1 4 899599991555 12 18
103 SUITE FAIL, SUBTRACT FAILURE TPNRM 200318 031124100 2 1499 177 123 1 4 999999999999 ORIGIN
103 SUITE FAIL, SUBTRACT FAILURE TPNRM 200318 031124200 2 1499 177 123 1 4 999999999999 ORIGIN
我尝试了如下示例 awk liner
awk 'BEGIN{print ("QSUM HEADER STOCK DATE TIME TI PNC JPS TNN LTNN PP JP AA NS FLAG OPCODE TYPE AI TI CPO")}
/^[0-9]/{print a["o"] " " a["p"] " " a["q"]
;delete a}
/^[0-9]/{a["o"]=$0
next}
/TNN/{getline
a["p"]=$0
next}
/NS/{getline
a["q"]=$0
next}
END
{print a["o"] " " a["p"] " " a["q"];}'
对于每种标头类型,我需要匹配模式,然后数组下一行。任何其他好的替代方案来实现此输出。这段代码对我有用,但这里的问题是与标头一样多,代码变得越来越长。
答案1
使用 GNU awk for FPAT
and FIELDWIDTHS
(然后利用 gawks 的\s/\S
简写[[:space:]]/[^[:space:]]
):
$ cat tst.awk
BEGIN { OFS="\t" }
/^\s*$/ { next } # skip blank lines
/^\s*QSUM\s/ { # start of a new record
numRecs++
lineNr = 0
}
{
if ( (++lineNr) % 2 == 1 ) {
# tags line so find every tag and field width
FPAT = "\\S+\\s*"
$0 = $0
for (i=1; i<=NF; i++) {
tag = $i
fw = (i>1 ? fw " " : "") (i<NF ? length(tag) : "*")
gsub(/^\s+|\s+$/,"",tag)
if ( !(tag in tagNames2nrs) ) {
tagNrs2names[++numTags] = tag
tagNames2nrs[tag] = numTags
}
fldNr2tagNr[i] = tagNames2nrs[tag]
}
FPAT = ""
}
else {
# vals line so use the field widths found for tags
FIELDWIDTHS = fw
$0 = $0
for (i=1; i<=NF; i++) {
val = $i
gsub(/^\s+|\s+$/,"",val)
tagNr = fldNr2tagNr[i]
vals[numRecs,tagNr] = val
}
FIELDWIDTHS = ""
}
}
END {
for (tagNr=1; tagNr<=numTags; tagNr++) {
tag = tagNrs2names[tagNr]
printf "%s%s", tag, (tagNr<numTags ? OFS : ORS)
}
for (recNr=1; recNr<=numRecs; recNr++) {
for (tagNr=1; tagNr<=numTags; tagNr++) {
val = vals[recNr,tagNr]
printf "%s%s", val, (tagNr<numTags ? OFS : ORS)
}
}
}
。
$ awk -f tst.awk file | column -s$'\t' -t
QSUM HEADER STOCK DATE TIME TI JPS TNN LTNN PP JP AA NS CPOODE TYPE AI PNC FLAG CPO
206 CC-USER REJECT SENT TNPPP 200322 104914600 8 6 0 1 4 899599991119 12 18
206 CC-USER REJECT SENT TNPPP 200322 115844000 8 6 0 1 4 899599991555 12 18
103 SUITE FAIL, SUBTRACT FAILURE TPNRM 200318 031124100 2 177 123 1 4 999999999999 1499 ORIGIN
103 SUITE FAIL, SUBTRACT FAILURE TPNRM 200318 031124200 2 177 123 1 4 999999999999 1499 ORIGIN
由于每对行都有不同的字段,其中一些字段为空,因此我们必须依靠固定字段来读取数据,但在尝试读取它们之前我们不知道有多少个字段以及它们的宽度。因此,该脚本使用 FPAT 查找每个标签行上的每个标签(又名名称、标题、标题):
TI JPS TNN LTNN PP JP
确定每个字段的宽度(包括标记名称和后面的空格),然后可以使用 FIELDWIDTHS 从后续值行读取值:
8 6 0 1
作为固定宽度字段,即使任何字段为空。
我不会评论它或以其他方式一般性地解释它,因为恕我直言,代码非常清楚,你问了很多文本操作问题,所以是时候通过阅读一些代码来了解 awk 了,查找一下手册页,print
在您需要查看变量具有哪些值的位置添加语句等。如果您有具体问题,那么当然请随时询问。
上述操作针对此输入文件运行,该文件与您的问题中的文件相同,但 QSUM、STOCK、DATA 和 TIME 标头的不一致对齐问题已得到修复,因为我不相信您的实际输入会那么混乱(但如果是的话,处理起来会很容易):
$ cat file
QSUM HEADER STOCK DATE TIME
206 CC-USER REJECT SENT TNPPP 200322 104914600
TI JPS TNN LTNN PP JP
8 6 0 1
AA NS CPOODE
4 899599991119
TYPE AI
12 18
QSUM HEADER STOCK DATE TIME
206 CC-USER REJECT SENT TNPPP 200322 115844000
TI JPS TNN LTNN PP JP
8 6 0 1
AA NS CPOODE
4 899599991555
TYPE AI
12 18
QSUM HEADER STOCK DATE TIME
103 SUITE FAIL, SUBTRACT FAILURE TPNRM 200318 031124100
TI PNC TNN PP JP AA
2 1499 177 123 1 4
NS FLAG
999999999999 ORIGIN
TI CPO
QSUM HEADER STOCK DATE TIME
103 SUITE FAIL, SUBTRACT FAILURE TPNRM 200318 031124200
TI PNC TNN PP JP AA
2 1499 177 123 1 4
NS FLAG
999999999999 ORIGIN
TI CPO
答案2
我想彻底解决这个问题。从 BEGIN 部分开始,创建一个包含所需数组列的数组输出, 为了。列宽和文本(首先):
BEGIN {
defCol[ 1] = " 5 QSUM";
defCol[ 2] = " 28 HEADER";
...
defCol[19] = " 6 CPO";
}
通过编写一个仅打印标题的函数来验证这一点。您可以像这样迭代 defCol:
printf ("%.*s", 0 + defCol[j], substr (defCol[j], 5);
然后编写一个函数,收集一个 QSUM 输入与下一个 QSUM 输入之间的一组输入行,并省略空行。仅包含大写字母和空格的行是标题,包含其他内容的行是数据,其中字段与前一个标题行对齐。 (我假设输入和输出中顶线的未对齐是一个拼写错误)。
对于每个组,您将标题与列名相匹配,以确定数据项属于哪个鸽子洞(自然地,作为索引与 defCol 相同的数组)。然后,您可以使用 defCol 索引对字段进行排序,并在 printf 格式化程序中使用 defCol 宽度来打印详细信息行。
这听起来很复杂,但它很灵活(看起来你在不久的将来还会遇到其他类似的问题),而且系统化。您还可以标记一些内容,例如您以前从未见过的标题、对于报告来说太长的字段等等。
更详细的功能说明:
首先,您需要一个条件来检测何时完成一个输入组——这曾经是“控制中断”。这会发生在包含“QSUM HEADER”或类似内容的行上,并使用某种模式来允许不同的间距。您也不希望第一次发生这种情况(因为您还没有组),并且您需要它也发生在 END 条件中(因为最后一个组没有标题来触发它)。
输入的其余部分可用于将所有标头部分附加到一个长字符串中,将所有数据部分附加到另一个长字符串中,并忽略所有空白行。
要输出组,您可以通过标题字符串查找数据标题的位置,并使用 match() 和 substr() 函数挑选数据字符串中相应位置的字段。您可以使用与标题项相同的索引将每个数据项存储在数组中。
然后,您可以输出数据字段,就像输出标题固定文本一样。
这一切听起来令人困惑,但其实相当简单。我现在无法编写代码,但今天晚些时候我可能可以发布一个框架。
实际上,由于间距的变化,输入(如发布的)可能无法可靠地解析。
例如,STOCK、DATE 和 TIME 下的值与值不一致。我们不能只对字段进行计数,因为 HEADER 有空格。我们要么依靠两个或多个空格作为分隔符,要么使用一些“足够接近”的字段对齐方式。这只是 40 行示例:还有其他危险。