使用 awk sed 和 grep 将多行文本转换为 csv
transform multiline text into csv with awk sed and grep
我运行一个shell命令returns像这样的重复值列表(注意缩进):
Name: vm346
cpu 1 (12%) 6150m (76%)
memory 1130Mi (7%) 1130Mi (7%)
Name: vm847
cpu 6 (75%) 30150m (376%)
memory 12980Mi (87%) 12980Mi (87%)
Name: vm848
cpu 3500m (43%) 17150m (214%)
memory 6216Mi (41%) 6216Mi (41%)
我正在尝试像这样转换数据(在 csv 中):
vm346,1,(12%),6150m,(76%),1130Mi,(7%),1130Mi,(7%)
vm847,6,(75%),30150m,(376%),12980Mi,(87%),12980Mi,(87%)
vm848,3500m,(43%),17150m,(214%),6216Mi,(41%),6216Mi,(41%)
问题是任何给定的数据集,如上面的数据集,总是在不止一行上。
当我将其通过管道输入 awk 时,它让我发疯,因为即使我使用:
BEGIN{ FS="\n" }
尝试将数据拼接成一行,但行不通。无论我做什么,awk 都会将 name 值作为分隔线保留在其他所有内容之上。
很抱歉,我没有太多代码可以分享,但我已经用这个转了几个小时了,我 运行 没有想法...
我可以用 Perl 解决这个问题:
perl -ane 'print join ",", @F[1 .. $#F]; print $F[0] eq "memory" ? "\n" : ","'
如果你需要的话,把它翻译成 awk 应该很容易。
它是如何工作的?
-a
将空白处的每一行拆分为@F 数组
-n
逐行读取输入并为每一行运行-e
之后指定的代码
- 我们打印所有元素,但第一个元素用逗号分隔(参见 join)
- 然后我们看第一列,如果是内存,我们在块的最后一行,所以我们打印一个换行符,否则我们打印一个逗号
这里有一个 ruby 可以做到这一点:
ruby -e '
s=$<.read
s.scan(/^([^ \t]+:)([\s\S]+?)(?=^|\z)/m). # parse blocks
map(&:last). # get data part
# parse and join the data fields:
map{|block| block.split(/\n[ \t]+[^ \t]+[ \t]+/)}.
map{|lines| lines.map(&:strip).join(" ").split().join(",")}.
each{|l| puts "#{l}"}
' file
vm346,1,(12%),6150m,(76%),1130Mi,(7%),1130Mi,(7%)
vm847,6,(75%),30150m,(376%),12980Mi,(87%),12980Mi,(87%)
vm848,3500m,(43%),17150m,(214%),6216Mi,(41%),6216Mi,(41%)
优点是不依赖于行数或字段数。它正在解析以下形式的块中的数据:
START: ([ \t]+[data_with_no_space])*\n
l1 ([ \t]+[data_with_no_space])*\n
...
START:
...
这样工作:
- 用THIS REGEX解析块;
- 保存数据元素的数组;
- 加入子数组,然后拆分成数据字段;
- Join(',') 生成 csv。
使用 AWK,一个选项是将 RS 设置为“名称:”,并忽略带有 NR > 1
的第一条记录,例如
awk -v RS="Name: " 'BEGIN{OFS=","} NR > 1 {print , , , , , , , , }' file
#> vm346,1,(12%),6150m,(76%),1130Mi,(7%),1130Mi,(7%)
#> vm847,6,(75%),30150m,(376%),12980Mi,(87%),12980Mi,(87%)
#> vm848,3500m,(43%),17150m,(214%),6216Mi,(41%),6216Mi,(41%)
awk '{=""}1' | paste -sd' \n' - | awk '{=}1' OFS=,
删除第一列。每三行加入一次。与 sed 相同的想法:
sed 's/^ *[^ ]* *//' | paste -sd' \n' - | sed 's/ */,/g'
其他:
awk '
=="Name:" {
sep=ors
ors=ORS
} {
for (i=2;i<=NF;++i) {
printf "%s%s",sep,$i
sep=OFS
}
} END {printf "%s",ors}'
或者,如果您想根据第一个字段为“内存”来打印 ORS(请注意,此程序可能会在不打印终止 ORS 的情况下结束):
awk '{for (i=2;i<=NF;++i) printf "%s%s",$i,(i==NF && =="memory" ? ORS : OFS)}'
其他:
awk -v OFS=, '
index([=14=],)==1 {
OFS=ors
ors=ORS
} {
=""
printf "%s",[=14=]
OFS=ofs
} END {printf "%s",ors} BEGIN {ofs=OFS}'
这可能对你有用 (GNU sed):
sed -nE '/^ +\S+ +/{s///;H;$!d};x;/./s/\s+/,/gp;x;s/^\S+ +//;h' file
总的来说,sed 程序处理缩进行、已收集的行(当前行是文件第一行的情况除外)和非缩进行。
关闭隐式打印并启用扩展正则表达式。 (-nE
).
如果当前行缩进,删除缩进,第一个字段和任何后续 spaces,将结果附加到保留 space,如果不是最后一行,删除它。
否则,检查保留 space 是否有聚集行,如果找到,用逗号替换一个或多个白色 space 并打印结果。然后通过删除第一个字段和任何后续 space 来准备当前行,并用结果替换保持 space。
该解决方案在逻辑上似乎是从后到前的,但是以这种方式编程可以避免多次检查文件末尾以及调用标签和 goto。
N.B。此解决方案适用于任意数量的缩进行。
我运行一个shell命令returns像这样的重复值列表(注意缩进):
Name: vm346
cpu 1 (12%) 6150m (76%)
memory 1130Mi (7%) 1130Mi (7%)
Name: vm847
cpu 6 (75%) 30150m (376%)
memory 12980Mi (87%) 12980Mi (87%)
Name: vm848
cpu 3500m (43%) 17150m (214%)
memory 6216Mi (41%) 6216Mi (41%)
我正在尝试像这样转换数据(在 csv 中):
vm346,1,(12%),6150m,(76%),1130Mi,(7%),1130Mi,(7%)
vm847,6,(75%),30150m,(376%),12980Mi,(87%),12980Mi,(87%)
vm848,3500m,(43%),17150m,(214%),6216Mi,(41%),6216Mi,(41%)
问题是任何给定的数据集,如上面的数据集,总是在不止一行上。
当我将其通过管道输入 awk 时,它让我发疯,因为即使我使用:
BEGIN{ FS="\n" }
尝试将数据拼接成一行,但行不通。无论我做什么,awk 都会将 name 值作为分隔线保留在其他所有内容之上。
很抱歉,我没有太多代码可以分享,但我已经用这个转了几个小时了,我 运行 没有想法...
我可以用 Perl 解决这个问题:
perl -ane 'print join ",", @F[1 .. $#F]; print $F[0] eq "memory" ? "\n" : ","'
如果你需要的话,把它翻译成 awk 应该很容易。
它是如何工作的?
-a
将空白处的每一行拆分为@F 数组-n
逐行读取输入并为每一行运行-e
之后指定的代码- 我们打印所有元素,但第一个元素用逗号分隔(参见 join)
- 然后我们看第一列,如果是内存,我们在块的最后一行,所以我们打印一个换行符,否则我们打印一个逗号
这里有一个 ruby 可以做到这一点:
ruby -e '
s=$<.read
s.scan(/^([^ \t]+:)([\s\S]+?)(?=^|\z)/m). # parse blocks
map(&:last). # get data part
# parse and join the data fields:
map{|block| block.split(/\n[ \t]+[^ \t]+[ \t]+/)}.
map{|lines| lines.map(&:strip).join(" ").split().join(",")}.
each{|l| puts "#{l}"}
' file
vm346,1,(12%),6150m,(76%),1130Mi,(7%),1130Mi,(7%)
vm847,6,(75%),30150m,(376%),12980Mi,(87%),12980Mi,(87%)
vm848,3500m,(43%),17150m,(214%),6216Mi,(41%),6216Mi,(41%)
优点是不依赖于行数或字段数。它正在解析以下形式的块中的数据:
START: ([ \t]+[data_with_no_space])*\n
l1 ([ \t]+[data_with_no_space])*\n
...
START:
...
这样工作:
- 用THIS REGEX解析块;
- 保存数据元素的数组;
- 加入子数组,然后拆分成数据字段;
- Join(',') 生成 csv。
使用 AWK,一个选项是将 RS 设置为“名称:”,并忽略带有 NR > 1
的第一条记录,例如
awk -v RS="Name: " 'BEGIN{OFS=","} NR > 1 {print , , , , , , , , }' file
#> vm346,1,(12%),6150m,(76%),1130Mi,(7%),1130Mi,(7%)
#> vm847,6,(75%),30150m,(376%),12980Mi,(87%),12980Mi,(87%)
#> vm848,3500m,(43%),17150m,(214%),6216Mi,(41%),6216Mi,(41%)
awk '{=""}1' | paste -sd' \n' - | awk '{=}1' OFS=,
删除第一列。每三行加入一次。与 sed 相同的想法:
sed 's/^ *[^ ]* *//' | paste -sd' \n' - | sed 's/ */,/g'
其他:
awk '
=="Name:" {
sep=ors
ors=ORS
} {
for (i=2;i<=NF;++i) {
printf "%s%s",sep,$i
sep=OFS
}
} END {printf "%s",ors}'
或者,如果您想根据第一个字段为“内存”来打印 ORS(请注意,此程序可能会在不打印终止 ORS 的情况下结束):
awk '{for (i=2;i<=NF;++i) printf "%s%s",$i,(i==NF && =="memory" ? ORS : OFS)}'
其他:
awk -v OFS=, '
index([=14=],)==1 {
OFS=ors
ors=ORS
} {
=""
printf "%s",[=14=]
OFS=ofs
} END {printf "%s",ors} BEGIN {ofs=OFS}'
这可能对你有用 (GNU sed):
sed -nE '/^ +\S+ +/{s///;H;$!d};x;/./s/\s+/,/gp;x;s/^\S+ +//;h' file
总的来说,sed 程序处理缩进行、已收集的行(当前行是文件第一行的情况除外)和非缩进行。
关闭隐式打印并启用扩展正则表达式。 (-nE
).
如果当前行缩进,删除缩进,第一个字段和任何后续 spaces,将结果附加到保留 space,如果不是最后一行,删除它。
否则,检查保留 space 是否有聚集行,如果找到,用逗号替换一个或多个白色 space 并打印结果。然后通过删除第一个字段和任何后续 space 来准备当前行,并用结果替换保持 space。
该解决方案在逻辑上似乎是从后到前的,但是以这种方式编程可以避免多次检查文件末尾以及调用标签和 goto。
N.B。此解决方案适用于任意数量的缩进行。