我有多行记录的数据,如下所示:
Name>Ami
Admin>2
Oper>1
Name>Sum
Admin>3
Total>2
Name>Tar
Admin>1
Oper>2
现在我想将这些记录折叠成单个 CSV 行,这些行应该只包含记录元素Name
、Admin
和 的“值”部分Oper
。对于该示例,最终输出应如下所示:
Ami,2,1
Sum,3,
Tar,1,2
我能够从中获取输出paste - - - -d,
,但我不想使用它,因为我想匹配Name
并将这些值放入第一列和Admin
第二列,然后Oper
放入第三列。
答案1
看来你想要
- 将多行记录折叠成单个 CSV 行,
- 只打印记录属性的值
Name
,Admin
并且Oper
- 并打印未给出这些属性之一的“显式”空字段。
我会推荐以下awk
程序:
awk -F'>' 'function printrec(){printf "%s,%s,%s\n",buf["Name"],buf["Admin"],buf["Oper"]}
(FNR>1 && $1=="Name"){printrec();delete buf}
{sub(/[[:space:]]*$/,"",$2); buf[$1]=$2}
END{printrec()}' input.txt
其工作原理如下:
输入文件的字段分隔符设置为
>
。记录的所有元素都存储在关联数组中
buf
。定义了一个函数,它以逗号分隔
printrec()
打印相关字段。buf
如果buf
不包含特定键,则引用它将计算为空字符串,从而满足您对缺失属性的空字段的要求。假设一条记录以一行开头
Name
。如果遇到这样的一行,并且它是不是文件中的第一行(FNR>1
),将打印先前缓冲的记录,并再次清除缓冲区。对于每一行,当前属性将存储在 中
buf
,其中“key”部分作为“数组索引”,“value”部分作为数组值。笔记我包含了一个
sub()
调用,以从您的输入示例包含的“值”部分中删除尾随空格。如果您确定现实中没有空格,则可以省略第 3 行的该部分。在文件末尾,将打印最终缓冲的记录。
将此程序应用于您的示例将产生
Ami,2,1
Sum,3,
Tar,1,2
笔记使用delete
数组需要 GNU awk
。如果您有不同的awk
口味,则需要使用
split("",buf)
作为解决方法。
此外,如果一条记录包含某个属性的多个实例(除非Name
始终将其视为记录开始),则后面出现的情况将覆盖以前出现的情况。
答案2
如果我明白你想要什么,那么你应该使用xargs
和awk
命令:
xargs -n3 < your_file.txt | awk '{gsub(/(Name|Admin|Oper)>/,""); print $1","$2","$3}' | awk -F',' --file script.awk
哪里script.awk
包含这个:
#! /usr/bin/awk
{
if($1 ~ ".*>.*") print ","$2","$3
else if($2 ~ ".*>.*") print $1",,"$3
else if($3 ~ ".*>.*") print $1","$2","
else print
}
如果your_file.txt
您有:
Name>Ami
Admin>2
Oper>1
Name>Sum
Admin>3
Total>2
Name>Tar
Admin>1
Oper>2
您xargs -n3
将每 3 行获得文件的输出(作为一行):
Name>Ami Admin>2 Oper>1
Name>Sum Admin>3 Total>2
Name>Tar Admin>1 Oper>2
具有awk '{gsub(/(Name|Admin|Oper)>/,""); print $1","$2","$3}'
像这样的值名称> 管理员> 或操作员>将被替换为空字符串,并且print $1","$2","$3
值(> 之后)将用逗号打印。
如果您使用:
xargs -n3 < data3 | awk '{gsub(/(Name|Admin|Oper)>/,""); print $1","$2","$3}
你会得到:
Ami,2,1
Sum,3,Total>2
Tar,1,2
现在,它将删除不必要的字符串,例如Total>2
.使用 ,script.awk
您可以删除它们,但在此之前我们必须定义分隔符,在本例中为逗号 ( ,
)。像这样的 awk 代码$1 ~ ".*>.*"
将验证当前字符串($1、$2 或 $3)是否与模式匹配.*>.*
,如果匹配,则不会打印当前字符串。
重要的:作品script.awk
以小组形式呈现。因此,如果存在无效列,则必须放置此列而不是姓名或者行政或者歌剧院。例如如果your_file.txt
有:
Name>Ami
Total>2
Oper>1
Name>Sum
Admin>3
Total>2
Total>Tar
Admin>1
Oper>2
该脚本的输出将是:
Ami,,1
Sum,3,
,1,2
但如果你里面有your_file.txt
:
Total>Ami
Total>2
Oper>1
Name>Sum
Admin>3
Total>2
Total>Tar
Admin>1
Total>2
该命令不会按您的预期工作。
笔记:如果您your_file.txt
也希望对其进行编辑,则必须tee your_file.txt
在最后使用:
xargs -n3 < your_file.txt | awk '{gsub(/(Name|Admin|Oper)>/,""); print $1","$2","$3}' | awk -F',' --file script.awk | tee your_file.txt
答案3
[不知道Name>...
记录是否有时会丢失,而不是系统地定期出现在要处理的数据中,我会假设它总是存在。]
基于awk的简单解决方案:
- 不使用中间数组,
- 不需要求助于定义函数:
- 修复了要处理的数据记录中尾随空格的问题
- 允许将任意数量的输入文件列为
awk
下面脚本的空格分隔参数。
主观上来说,虽然可能不如@AdminBee的答案那么优雅,但更容易阅读。
$ awk -F'>' '($1 == "Name") {
if (NR>1) printf "\n";
gsub(" ","",$2);
printf "%s%s", $2,","}
($1 == "Admin") {
gsub(" ","",$2);
printf "%s%s", $2,","}
($1 == "Oper") {
gsub(" ","",$2);
printf "%s", $2}
END {printf "\n"}' input_file
Ami,2,1
Sum,3,
Tar,1,2
在上面,actiongsub(" ","",$2)
抑制了第二个字段中的所有空格,表示为$2
。 (OP 包括那些尾随空格,它们在显示结果时会造成严重破坏。)
答案4
如果输入数据看起来更像这样(rec 格式):
Name: Ami
Admin: 2
Oper: 1
Name: Sum
Admin: 3
Total: 2
Name: Tar
Admin: 1
Oper: 2
...然后我们可以轻松地用来rec2csv | csvcut -C Total
生成 CSV 文档
Name,Admin,Oper
Ami,2,1
Sum,3,
Tar,1,2
这rec2csv
是 GNU recutils 中的一个实用程序,它将重新格式化的数据重新格式化为 CSV,并且csvcut
是一个从 CSV 中选择列的实用程序(这里用于排除列Total
),来自 csvkit。
可以使用 将原始数据转换为 re-formatted 格式awk
,然后将其输入rec2csv
和csvcut
:
awk '/^Name>/ { print "" } { sub(">",": "); print }' file | rec2csv | csvcut -C Total
这只是确保以 开头的每一行Name>
前面有一个空行,然后替换每行中第一次出现>
的。:
tail -n +2
如果您想删除 CSV 标头,请传递结果。
这显然假设Name>
原始文件中的每次出现都会引入一条新记录。
如果输入看起来更像这样(xtab 格式):
Name Ami
Admin 2
Oper 1
Name Sum
Admin 3
Total 2
Name Tar
Admin 1
Oper 2
然后我们可以使用mlr
(Miller) 首先“unsparsify”每个记录(为缺失的键分配空值),然后以 CSV 输出格式提取所需的键:
mlr --ixtab unsparsify | mlr --ocsv cut -f Name,Admin,Total
我们可以使用与上一个类似的命令将原始数据转换为 xtab 格式awk
,然后通过 Miller 将其输入:
awk '/^Name>/ { print "" } { sub(">"," "); print }' file | mlr --ixtab unsparsify | mlr --ocsv cut -f Name,Admin,Oper
这会给你
Name,Admin,Oper
Ami,2,1
Sum,3,
Tar,1,2
使用mlr
(管道中的最后一个)及其-N
选项来删除 CSV 标头。