我正在尝试使用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}
提前感谢您对此的看法。
答案 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