我有一个场景,需要反转数据以实现父子关系。我的源数据如下:
Key_Col|层次结构
1|a、b、c、d
2|a、b、c、d、e
我的预期输出如下:
关键校 | 孩子 | 家长 |
---|---|---|
1 | d | C |
1 | C | 乙 |
1 | 乙 | A |
1 | A | 无效的 |
2 | e | d |
2 | d | C |
2 | C | 乙 |
2 | 乙 | A |
2 | A | 无效的 |
您能否让我知道如何通过 bash 脚本实现此目的?
我使用的脚本是:
Var="1|a,b,c,d";
for i in $Var
do
Key=`echo $i |cut -d'|' -f1`
Hierarchy=`echo $i |cut -d'|' -f2`
Delim_Count=`echo ${Hierarchy} |awk -F',' '{ print NF-1 }'`
for (( c=$Delim_Count+1; c>=1; c-- ))
do
Parent=`echo ${Hierarchy} |cut -d',' -f$c`
Prev=`expr $c - 1`
if [ $Prev -ne 0 ]; then
Child=`echo ${Hierarchy} |cut -d',' -f${Prev}`
echo "${Key}|${Parent}|${Child}"
else
echo "${Key}|${Parent}|"
fi
done
done
但问题是,如果超过 100 行,脚本就需要很长时间才能完成。
答案1
使用用于处理文本或结构化数据的语言通常更容易完成此类事情。以下是使用标准文本处理实用程序的解决方案awk
和使用磨坊主( mlr
),专门用于处理结构化数据的工具(您的数据看起来像 CSV)。
和awk
:
$ cat file
Key_Col|Hierarchy
1|a,b,c,d
2|a,b,c,d,e
$ awk 'BEGIN { OFS=FS="|" } NR == 1 { print $1, "Child", "Parent"; next } { n=split($2,a,","); a[0]="null"; for (i=n;i>0;i--) print $1,a[i],a[i-1] }' file
Key_Col|Child|Parent
1|d|c
1|c|b
1|b|a
1|a|null
2|e|d
2|d|c
2|c|b
2|b|a
2|a|null
上面的代码awk
将每个输入行读取为一组|
- 分隔的字段。它将逗号上的第二个字段拆分到数组中a
。数组的第 0 个元素设置为字符串null
(split()
创建一个第一个索引为 1 的数组,所以我们知道我们可以使用索引 0 而不会覆盖数据)。然后,我们从数组末尾迭代到开头,输出第一个字段的值以及当前数组元素和数组中的前一个元素。当我们到达最后一次迭代时,循环变量的值为 1,这会导致打印a[1]
and a[0]
( ) 。null
包含标题的第一行输入的处理方式有所不同。该代码不是拆分等,而是打印三个字段:输入中的第一个字段以及字符串Child
和各一个字段Parent
。条件NR==1
块执行此操作。
为了可读性而重新格式化的代码awk
:
BEGIN {
OFS = FS = "|"
}
NR == 1 {
print $1, "Child", "Parent"
next
}
{
n = split($2, a, ",")
a[0] = "null"
for (i = n; i > 0; i--)
print $1, a[i], a[i-1]
}
由于输入看起来像 CSV,因此使用支持 CSV 的工具来处理它可能会更安全。 Miller( mlr
) 就是这样一个工具:
$ mlr --csv --fs pipe put -q 'm=splitnv($Hierarchy,","); m[0]="null"; for (var i=length(m)-1;i>0;i-=1) { emit {"Key_Col": $Key_Col, "Child": m[i], "Parent": m[i-1] } }' file
Key_Col|Child|Parent
1|d|c
1|c|b
1|b|a
1|a|null
2|e|d
2|d|c
2|c|b
2|b|a
2|a|null
Millerput
表达式遵循与上述代码相同的轮廓awk
,但不必将标头作为特殊情况处理,因为 Miller 知道如何读取和使用这些标头:
m = splitnv($Hierarchy, ",")
m[0] = "null"
for (var i = length(m) - 1; i > 0; i -= 1) {
emit {
"Key_Col": $Key_Col,
"Child": m[i],
"Parent": m[i-1]
}
}
put
Miller 允许我们通过调整子命令之前的选项来产生多种不同形式的结果。
漂亮打印的“禁止”输出:
$ mlr --c2p --barred --ifs pipe put ...as above...
+---------+-------+--------+
| Key_Col | Child | Parent |
+---------+-------+--------+
| 1 | d | c |
| 1 | c | b |
| 1 | b | a |
| 1 | a | null |
| 2 | e | d |
| 2 | d | c |
| 2 | c | b |
| 2 | b | a |
| 2 | a | null |
+---------+-------+--------+
JSON:
$ mlr --c2j --ifs pipe put ...as above...
{ "Key_Col": 1, "Child": "d", "Parent": "c" }
{ "Key_Col": 1, "Child": "c", "Parent": "b" }
{ "Key_Col": 1, "Child": "b", "Parent": "a" }
{ "Key_Col": 1, "Child": "a", "Parent": "null" }
{ "Key_Col": 2, "Child": "e", "Parent": "d" }
{ "Key_Col": 2, "Child": "d", "Parent": "c" }
{ "Key_Col": 2, "Child": "c", "Parent": "b" }
{ "Key_Col": 2, "Child": "b", "Parent": "a" }
{ "Key_Col": 2, "Child": "a", "Parent": "null" }
(ETC。)
答案2
使用乐(以前称为 Perl_6)
~$ raku -e 'my @a; for lines() {.split("|") andthen @a.push: .[0] => ("null".Slip, .[1].split(",").Slip) }; \
put .invert.invert.join("\n") for [Z=>] @a.map(*.key), @a.map(*.value.rotor(2 => -1) );' file
上面是用 Raku(Perl 编程语言家族的成员)编写的答案。简而言之,在第一条语句中声明了一个数组。在第二条语句中,通过首先在bar 上获取键来lines()
读取“哈希数组” ,然后用逗号分割值。 A添加到每个值系列的开头。由于 Raku 不会自动展平数组元素(值得庆幸的是),展平是通过调用.在最后的语句中,“空”填充的值被重叠地填充,每行的键和值被压缩成对,被编辑两次以将键扩展到每个关联的值,然后用or打印。split
|
,
"null"
.Slip
rotor
[Z=>]
invert
say
put
输入示例:
1|a,b,c,d
2|a,b,c,d,e
3|a,b,c
示例输出(非反转列):
1 null a
1 a b
1 b c
1 c d
2 null a
2 a b
2 b c
2 c d
2 d e
3 null a
3 a b
3 b c
.skip
如果您需要处理标题行,一种简单的方法是添加对after的调用lines
(默认跳过一行),然后使用 重新添加您选择的标题say()
。
最后,OP的要求实际上是颠倒列的顺序,这可以通过在最后一个语句中插入调用来完成,编写as.reverse
的最终准备工作。@a.map(*.value.rotor(2 => -1)
@a.map(*.value.reverse.rotor(2 => -1)
示例输出(反转列):
1 d c
1 c b
1 b a
1 a null
2 e d
2 d c
2 c b
2 b a
2 a null
3 c b
3 b a
3 a null