从广泛到长格式的重塑

我试图用unix来将制表符分隔的文件从短/宽格式转换为长格式,类似于R中的reshape函数。我希望为起始文件中的每一行创build三行。 第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来查找逗号replace为硬回车

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}

在此先感谢您对此的看法。

您可以为此写一个简单的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 

作为解决方案,无需调用外部程序:

 #!/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" 

如果你不需要在第四列的一组内输出任何特定的顺序,那么下面的awk单行可能会这样做:

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

这是通过将你的第四列分成一个数组,然后为数组的每个元素打印“新”四列。

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

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

但这是awk。 而你在问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选择显然是相似的,但是阅读和编码要麻烦得多。

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

还要注意,没有理由将所有的输入复制到一个数组中,我只是用这种方式来演示更多的mapfile 。 你当然可以使用旧的标准,

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

如果你更喜欢。