首页 文章

从宽到长格式重塑

提问于
浏览
0

我试图使用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}

提前感谢您对此的看法 .

3 回答

  • 0

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

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

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

    如果您不需要输出在第四列组中的任何特定顺序,则以下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 替代方案相似,但读取和编码要麻烦得多 .

    注意 printf 行上的 ${v%$'\n'} . 这剥离了由 mapfile 剥离的最后一个字段's trailing newline, which doesn',因为我们正在使用备用分隔符 .

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

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

    如果你更喜欢 .

相关问题