从宽到长的格式重塑

时间:2017-01-25 02:12:29

标签: bash parsing unix

我正在尝试使用unix将制表符分隔文件从短/宽格式转换为长格式,方式与R中的reshape函数类似。我希望为起始文件中的每一行创建三行。第4列当前包含3个以逗号分隔的值。我希望第1列,第2列和第3列对于每个起始行保持相同,但第4列是初始列4中的值之一。这个例子可能使它比我口头描述的更清晰:

current file:  
A1  A2  A3  A4,A5,A6  
B1  B2  B3  B4,B5,B6  
C1  C2  C3  C4,C5,C6  

goal:  
A1  A2  A3  A4  
A1  A2  A3  A5  
A1  A2  A3  A6  
B1  B2  B3  B4  
B1  B2  B3  B5  
B1  B2  B3  B6  
C1  C2  C3  C4  
C1  C2  C3  C5  
C1  C2  C3  C6  

当有人刚刚熟悉这种语言时,我最初的想法是使用sed来找到用硬回复替换的逗号

sed 's/,/&\n/' data.frame

我真的不确定如何包含第1-3列的值。我对此工作寄予厚望,但我唯一能想到的是尝试使用{print $ 1,$ 2,$ 3}插入列值。

sed 's/,/&\n{print $1, $2, $3}/' data.frame

令我惊讶的是,输出看起来像这样:

A1  A2  A3  A4  
{print $1, $2, $3}  A5  
{print $1, $2, $3}  A6  
B1  B2  B3  B4  
{print $1, $2, $3}  B5  
{print $1, $2, $3}  B6  
C1  C2  C3  C4  
{print $1, $2, $3}  C5  
{print $1, $2, $3}  C6  

似乎一种方法可能是存储列1-3的值然后插入它们。我不确定如何存储值,我认为它可能涉及使用以下脚本的改编,但我很难理解所有组件。

NR==FNR{a[$1, $2, $3]=1}

提前感谢您对此的看法。

4 个答案:

答案 0 :(得分:1)

您可以为此编写简单的read循环,并使用大括号扩展来解析逗号分隔的字段:

#!/bin/bash

while read -r f1 f2 f3 c1; do
  # split the comma delimited field 'c1' into its constituents
  for c in ${c1//,/ }; do
     printf "$f1 $f2 $f3 $c\n"
  done
done < input.txt

输出:

A1 A2 A3 A4
A1 A2 A3 A5
A1 A2 A3 A6
B1 B2 B3 B4
B1 B2 B3 B5
B1 B2 B3 B6
C1 C2 C3 C4
C1 C2 C3 C5
C1 C2 C3 C6

答案 1 :(得分:1)

作为没有调用外部程序的解决方案:

#!/bin/bash

data_file="d"

while IFS=" " read -r f1 f2 f3 r
do
  IFS="," read f4 f5 f6 <<<"$r"
  printf "$f1 $f2 $f3 $f4\n$f1 $f2 $f3 $f5\n$f1 $f2 $f3 $f6\n"
done <"$data_file"

答案 2 :(得分:0)

如果您不需要输出在第四列的组中以任何特定顺序排列,则以下awk单行可能会执行:

awk '{split($4,a,","); for(i in a) print $1,$2,$3,a[i]}' input.txt

这可以通过将第4列拆分为数组,然后对于数组的每个元素,打印“新”四列。

如果订单很重要 - 也就是说,A4必须在A5之前等,那么你可以使用经典的for循环:

awk '{split($4,a,","); for(i=1;i<=length(a);i++) print $1,$2,$3,a[i]}' input.txt

但那太糟糕了。而且你在问bash。

以下可能有效:

#!/usr/bin/env bash

mapfile -t arr < input.txt

for s in "${arr[@]}"; do
  t=($s)
  mapfile -t -d, u <<<"${t[3]}"
  for v in "${u[@]}"; do
    printf '%s %s %s %s\n' "${t[@]:0:3}" "${v%$'\n'}"
  done
done

这会将整个输入文件复制到数组的元素中,然后逐步执行该数组,将每个第4列映射到第二个数组。然后逐步执行第二个数组,打印第一个数组中的前三列,以及第二个数组中的当前字段。

它在结构上与awk替代方案明显相似,但阅读和编码更加繁琐。

请注意${v%$'\n'}行上的printf。这剥离了最后一个字段的尾随换行符,它不会被mapfile剥离,因为我们正在使用备用分隔符。

另请注意,您没有理由 将所有输入复制到数组中,我只是这样做以演示更多mapfile。你当然可以使用旧标准,

while read s; do
   ...
done < input.txt

如果您愿意。

答案 3 :(得分:0)

在伟大的Miller中,nest verb可以做到

使用

mlr --nidx --ifs "\t" nest --explode --values  --across-records -f 4 --nested-fs ","  input.tsv

您将拥有

A1 A2 A3 A4
A1 A2 A3 A5
A1 A2 A3 A6
B1 B2 B3 B4
B1 B2 B3 B5
B1 B2 B3 B6
C1 C2 C3 C4
C1 C2 C3 C5
C1 C2 C3 C6