Bash / Awk:使用多个分隔符重新格式化不均匀的列

时间:2016-06-10 18:47:02

标签: bash csv awk

我有一个CSV,我需要重新格式化单个列的内容。 问题是每个单元格的长度完全不同,需要重新格式化。

当前列看起来像(这是两列单列):

Foo*foo*foo*1970,1980+Bar*bar*bar*1970
Foobar*Foobar*foobarbar*1970,1975,1980

结果看起来像(仍然是两行一列)

Foo*foo*foo*1970+Foo*foo*foo*1980+Bar*bar*bar*1970
Foobar*Foobar*foobarbar*1970+Foobar*Foobar*foobarbar*1975+Foobar*Foobar*foobarbar*1980

这就是我想要做的事情

#!/bin/bash

cat foocol | \
    awk -F'+' \
    '{for i in NF print $i}' \
        | awk -F'*' \
        '{$Foo=$1"*"$2"*"$3"*" print $4}' \
\
        | awk -v Foo=$Foo -F',' \
        '{for j in NF do \
            print Foo""$j"+" }' \
> newcol

这个想法是迭代多个' +'分隔数据,而前三个' *'定界值将按每个''进行分组。分隔年份,用' +'他们之间

但我到处都是语法错误。

由于

2 个答案:

答案 0 :(得分:1)

$ awk --re-interval -F, -v OFS=+ '{match($1,/([^*]*\*){3}/);
                 prefix=substr($0,RSTART,RLENGTH); 
                 for(i=2;i<=NF;i++) $i=prefix $i }1' file

Foo*foo*foo*1970+Foo*foo*foo*1980+Bar*bar*bar*1970  
Foobar*Foobar*foobarbar*1970+Foobar*Foobar*foobarbar*1975+Foobar*Foobar*foobarbar*1980

或许可以使用if(match(...

添加验证

答案 1 :(得分:1)

TXR中的解决方案:

$ txr reformat.txr  data
Foo*foo*foo*1970+Foo*foo*foo*1980+Bar*bar*bar*1970
Foobar*Foobar*foobarbar*1970+Foobar*Foobar*foobarbar*1975+Foobar*Foobar*foobarbar*1980

reformat.txr中的代码:

@(repeat)
@  (coll)@/\+?/@a*@b*@c*@(coll)@{x /[^,+]+/}@(until)+@(end)@(end)
@  (output :into items)
@    (repeat)
@      (repeat)
@a*@b*@c*@x
@      (end)
@    (end)
@  (end)
@  (output)
@    {items "+"}
@  (end)
@(end)

此解决方案基于具有嵌套语法的数据:记录组由换行符分隔。组内的记录由+分隔,在记录中有四个以*分隔的字段。最后一个字段包含以逗号分隔的项目。通过扩展记录副本来规范化数据,使逗号分隔的项目分布在副本中。

外部@(repeat)手柄越过线条。外部@(coll)遍历记录,将前三个字段收集到变量abc中。然后内部@(coll)将每个逗号分隔的项目放入变量x。内部@(coll)x - s收集到一个列表中,外部@(coll)也将所有变量收集到列表中,因此ab,{ {1}}成为字符串列表,c是字符串列表的列表。

x中的:into items关键字参数会导致通常将标准输出设备的行收集到字符串列表中,并绑定到变量。例如:

output

建立一个变量@(output :into lines) a b cd @(end) ,其中包含列表lines

所以这里我们将双嵌套("a" "b" "cd")的输出作为一堆行,其中每一行代表一条记录,存储在一个名为repeat的变量中。然后我们items使用output,这是一种语法,用给定的分隔符输出列表变量的内容。

双重嵌套@{items "+"}处理每个逗号分隔项与第四个字段的记录扩展。外部repeat隐式迭代列表repeatabc。在x内,这些变量表示各自列表中的项目。变量repeat是一个列表列表,因此内部x会对其进行迭代。在外部repeat内,变量repeatab已经是标量,并且保持在内部c的范围内:仅repeat 1}}变化,这正是我们想要的。

在每行的数据收集中,有一些细微之处:

x

首先,我们将可选的前导加上与@ (coll)@/\+?/@a*@b*@c*@(coll)@{x /[^,+]+/}@(until)+@(end)@(end) 正则表达式匹配,从而消耗它。如果没有这个,除了第一个记录之外,每个记录的/\+?/字段将包括分隔a,我们将在最终输出中得到双+ - s。简单地匹配+ab变量。 TXR对于分隔材料非贪婪:c表示将某些字符与最近的@a*匹配,并将它们绑定到变量*。收集a列表更加棘手。这里使用正 - 正则表达式匹配变量:x来提取子字段。每个@{x /[^,+]+/}是一个或多个字符的序列,这些字符不是加号或逗号,在不考虑后续内容的情况下正向提取,就像标记器提取标记一样。此内部集合在遇到x时终止,这是+子句确保的内容。如果它到达行尾,它也会隐式终止; @(until)+匹配不是强制性的(默认情况下)。终止@(until)会停留在输入流中,这就是为什么我们必须识别它并将其丢弃在+前面。

应该注意的是,默认情况下,@a会扫描匹配项并跳过不匹配的文本区域,就像它的堂兄@(coll)一样。例如,如果我们有@(collect),它会将小写字母序列收集到@(coll)@{foo /[a-z]+/}@(end)中,将foo转换为此类字符串列表,如果输入为foo,然后1234abcd-efgh.... ijkfoo列表结束。这就是为什么内部("abcd" "efgh" "ijk")中没有明确的逻辑来使用分隔逗号:它们被隐式跳过。