我有完整的文件列表,其中包含以下文本。
Sun Aug 22 19:00:00 2021
User-Name = "407359687"
Acct-Status-Type = Interim-Update
Acct-Output-Octets = 3263901190
Acct-Session-Id = "PPP3092201SSG0001006b0a55AABODS"
Acct-Session-Time = 1146851
Acct-Output-Gigawords = 15
Event-Timestamp = "Aug 22 2021 18:55:32 +08"
Timestamp = 1629630000
我的目标是获取重要的行并将其保存到新的 CSV 文件中,我使用下面的 AWK 命令对文本中的值进行排序,但我不知道将其写入 CSV 文件中。
awk '{if ($1 == "User-Name")
{start=1; wholeLine=""; wholeLine = wholeLine$3;}
if ($1$2$3 =="Acct-Status-Type=Interim-Update"||$1$2$3 =="Acct-Status-Type=Stop")
{wholeLine=wholeLine","$3;}
else if ($1$2$3 =="Acct-Status-Type=Start")
{start=0;wholeLine=""}
if (($1=="Acct-Output-Octets")&&(start==1))
{wholeLine=wholeLine","$3;}
if (($1=="Acct-Session-Id")&&(start==1))
{wholeLine=wholeLine","$3;}
if (($1=="Acct-Session-Time")&&(start==1))
{wholeLine=wholeLine","$3;}
if (($1=="Acct-Output-Gigawords")&&(start==1))
{wholeLine=wholeLine","$3;}
if (($1=="Event-Timestamp")&&(start==1))
{timeStamp="";timeStamp=$3" "$4" "$5" "$6" "$7;wholeLine=wholeLine","timeStamp}
if (($1=="Timestamp")&&(start==1))
{wholeLine=wholeLine","$3;}
if (($1=="")&&(start==1))
{start=0;print wholeLine}}' /home/file/detail-20210822
我预期的 CVS 结果应该是这样的。
"405947674",Interim-Update,1079493624,"PPP3082110SSG000100be4a72AAAk5Y",25440,0,"Aug 22 2021 19:00:43 +08",1629630315
答案1
假设每条记录的八个字段始终存在,顺序正确,并且不需要进一步处理即可在 CSV 文件中有效(即不需要额外的引用或转义):
sed -n 's/^[^=]*= //p' file | paste -d , - - - - - - - -
这将删除任何不包含任何等号后跟空格的行(或=
子字符串不包含该行中第一个等号的行),然后删除第一个等号之后的空格之前的任何文本。
然后,它用于paste
创建剩余数据的八个逗号分隔列。
对包含示例数据的文件进行两次测试:
$ sed -n -e 's/^[^=]*= //p' file | paste -d , - - - - - - - -
"407359687",Interim-Update,3263901190,"PPP3092201SSG0001006b0a55AABODS",1146851,15,"Aug 22 2021 18:55:32 +08",1629630000
"407359687",Interim-Update,3263901190,"PPP3092201SSG0001006b0a55AABODS",1146851,15,"Aug 22 2021 18:55:32 +08",1629630000
可以通过将结果通过管道来删除Start
第二列中的任何行(以过滤掉原始数据中的部分)Acct-Status-Type = Start
awk -F , '$2 != "Start"'
答案2
以下是如何真正将文件转换为 CSV,假设分隔记录的唯一因素是样本输入顶部的时间戳,并且每个记录都包含所有相同的标签(也称为名称/键/符号的左侧=
)数据):
$ cat tst.awk
BEGIN { OFS="," }
/=/ {
gsub(/^[[:space:]]+|[[:space:]]+$/,"")
tag = val = $0
sub(/[[:space:]]*=.*/,"",tag)
sub(/[^=]*=[[:space:]]*/,"",val)
if ( !(tag in tag2val) ) {
tags[++numTags] = tag
}
tag2val[tag] = val
next
}
NR>1 { prt() }
END { prt() }
function prt( tagNr, tag, val) {
if ( !doneHdr++ ) {
for (tagNr=1; tagNr<=numTags; tagNr++) {
tag = sanitize(tags[tagNr])
printf "%s%s", tag, (tagNr<numTags ? OFS : ORS)
}
}
for (tagNr=1; tagNr<=numTags; tagNr++) {
tag = tags[tagNr]
val = sanitize(tag2val[tag])
printf "%s%s", val, (tagNr<numTags ? OFS : ORS)
}
numTags = 0
delete tag2val
}
function sanitize(inStr, outStr) {
outStr = inStr
if ( outStr ~ ("[" OFS "\"]") ) {
gsub(/^"|"$/,"",outStr)
gsub(/"/,"\"\"",outStr)
outStr = "\"" outStr "\""
}
return outStr
}
$ awk -f tst.awk file
User-Name,Acct-Status-Type,Acct-Output-Octets,Acct-Session-Id,Acct-Session-Time,Acct-Output-Gigawords,Event-Timestamp,Timestamp
"407359687",Interim-Update,3263901190,"PPP3092201SSG0001006b0a55AABODS",1146851,15,"Aug 22 2021 18:55:32 +08",1629630000
将其写入文件与将任何其他命令输出写入文件相同:
awk -f tst.awk file > output.csv
即使您的输入值或标签包含=
、"
、,
s 或除换行符之外的任何其他字符,上述内容也将输出正确、有效的 CSV。
如果您实际上不需要标题行,则只需从函数if ( !doneHdr++ )
中删除该块即可prt()
。
答案3
我会先
awk -F= 'NF==2{printf "%s%s",comma,substr($2,2);comma=","} END {printf "\n" }' source > dest
在哪里
-F=
用作=
分隔符NF==2
选择包含 2 个字段的行substr($2,2)
去除前导空格source
和dest
是源文件和目标文件。
如果你想保留你的程序,你可以替换
if (($1=="Acct-Session-Id")&&(start==1))
{wholeLine=wholeLine","$3;}
经过
$1 ~ /Acct-Session-Id/ && (start==1) {wholeLine=wholeLine","substr($2,2);}
并删除封闭的 { ... } 正如@berndbausch 指出的那样。
答案4
使用 Raku(以前称为 Perl_6)
raku -e 'my @array; for lines() {@array.push($_) if /User\-Name/ fff /<!after Event\-> Timestamp/};
@array>>.split(/^^ .+? " = "/, :skip-empty).batch(8).map(*.join(",")).join("\n").put;'
输入示例(@FelixJN 之后):
Sun Aug 22 19:00:00 2021
User-Name = "407359687"
Acct-Status-Type = Interim-Update
Acct-Output-Octets = 3263901190
Acct-Session-Id = "PPP3092201SSG0001006b0a55AABODS"
Acct-Session-Time = 1146851
Acct-Output-Gigawords = 15
Event-Timestamp = "Aug 22 2021 18:55:32 +08"
Timestamp = 1629630000
RANDOM ANNOYANCE
AND AN EMPTY LINE
Sun Aug 22 19:00:00 2021
User-Name = "407359687"
Acct-Status-Type = Interim-Update
Acct-Output-Octets = 3263901190
Acct-Session-Id = "PPP3092201SSG0001006b0a55AABODS"
Acct-Session-Time = 1146851
Acct-Output-Gigawords = 15
Event-Timestamp = "Aug 22 2021 18:55:32 +08"
Timestamp = 1629630000
示例输出:
"407359687",Start,3263901190,"PPP3092201SSG0001006b0a55AABODS",1146851,15,"Aug 22 2021 18:55:32 +08",1629630000
"407359687",Interim-Update,3263901190,"PPP3092201SSG0001006b0a55AABODS",1146851,15,"Aug 22 2021 18:55:32 +08",1629630000
虽然我认识到 OP 要求awk
解决方案,但 Perl 语言家族以文本处理而闻名。上面的“one-liner”Raku 代码使用fff
“触发器”运算符来捕获两个哨兵行之间的文本,第一行匹配“用户名”行,第二行匹配“时间戳”行。负向后查找<!after Event\->
用于确保正则表达式不会错误地识别“Event-Timestamp”行。
选定的行被推入@array
,然后split()
用于删除所需值左侧的所有内容。记录batch()
以 8 组(列)为单位,并map()
使用逗号对join()
值进行调用。连续的记录行由换行符连接。
删除Start
第二列中的任何 CSV 行可以通过通过以下管道传输上述单行来完成:
raku -ne '.put unless .split(",")[1] eq "Start";'
如果 CSV 将成为您计算生活的主要部分,那么这是一个很好的起点。 Raku 有许多CSV
模块可以帮助您处理更复杂的 CSV 案例。