根据不同的gsub条件同时gsub许多列?

时间:2017-01-03 17:10:10

标签: bash loops awk gsub

我有一个包含以下数据的文件 -

输入 -

A B C D E F
A B B B B B
C A C D E F
A B D E F A
A A A A A F
A B C B B B

如果从第2行开始的任何其他行与第1行具有相同的字母,则应将它们更改为1.基本上,我试图找出任何行与第一行的相似程度。< / p>

期望的输出 -

1 1 1 1 1 1
1 1 B B B B
C A 1 1 1 1
1 1 D E F A
1 A A A A 1
1 1 1 B B B

第一行已全部为1,因为它与自身相同(显然)。在第二行中,第一列和第二列与第一行(A B)相同,因此它们变为1 1。等等其他行。

我编写了以下用于执行此转换的代码 -

for seq in {1..1} ; #Iterate over the rows (in this case just row 1)
do 
    for position in {1..6} ; #Iterate over the columns
    do 
        #Define the letter in the first row with which I'm comparing the rest of the rows
        aa=$(awk -v pos=$position -v line=$seq 'NR == line {print $pos}' f) 
        #If it matches, gsub it to 1 
        awk -v var=$aa -v pos=$position '{gsub (var, "1", $pos)} 1' f > temp
        #Save this intermediate file and now act on this
        mv temp f 
    done 
done

可以想象,这非常慢,因为嵌套循环很昂贵。我的真实数据是60x10000矩阵,此程序运行大约需要2个小时。

我希望你能帮助我摆脱内循环,这样我就可以一步完成所有6个gsubs。也许把它们放在自己的阵列中?我的awk技能还不是很好。

2 个答案:

答案 0 :(得分:4)

您可以使用这个更简单的awk命令来完成更快完成的工作,因为我们避免在shell中嵌套循环并在嵌套循环中重复调用awk:

awk '{for (i=1; i<=NF; i++) {if (NR==1) a[i]=$i; if (a[i]==$i) $i=1} } 1' file

1 1 1 1 1 1
1 1 B B B B
C A 1 1 1 1
1 1 D E F A
1 A A A A 1
1 1 1 B B B

修改

根据下面的评论,您可以采取哪些措施来获取每一行中每列的总和:

awk '{sum=0; for (i=1; i<=NF; i++) { if (NR==1) a[i]=$i; if (a[i]==$i) $i=1; sum+=$i}
      print $0, sum}' file

1 1 1 1 1 1 6
1 1 B B B B 2
C A 1 1 1 1 4
1 1 D E F A 2
1 A A A A 1 2
1 1 1 B B B 3

答案 1 :(得分:3)

<强>输入

$ cat f
A B C D E F
A B B B B B
C A C D E F
A B D E F A
A A A A A F
A B C B B B

所需的o / p

$ awk 'FNR==1{split($0,a)}{for(i=1;i<=NF;i++)if (a[i]==$i) $i=1}1' f
1 1 1 1 1 1
1 1 B B B B
C A 1 1 1 1
1 1 D E F A
1 A A A A 1
1 1 1 B B B

解释

  • FNR==1{ .. }

awk读取当前文件的第一条记录时,请在大括号内执行操作

  

split(string,array [,fieldsep [,seps]])

     

将字符串分成由fieldsep分隔的片段并存储片段   in array和seps数组中的分隔符字符串。

  • split($0,a)
  

通过fieldsep将当前记录或行($0)拆分成碎片(defualt space,as   我们还没有提供第三个参数)并将这些部分存储在数组a中   因此数组a包含第一行的数据

       a[1] = A 
       a[2] = B
       a[3] = C 
       a[4] = D  
       a[5] = E  
       a[6] = F
  • for(i=1;i<=NF;i++)
  

遍历文件的每个记录的所有字段,直到文件末尾。

  • if (a[i]==$i) $i=1
  

如果第一行的当前索引(i)的列值等于   当前行的当前列值设置当前列值= 1(表示修改当前列值)

现在我们修改了列值,然后只打印修改后的行

  • }1

    1始终评估为true,执行默认操作{print $0}

有关评论的更新请求

  

同样的问题,我有一个程序的第二部分加起来   行中的数字。即你会得到6,2,4,2,2,3   输出。您的程序是否可以调整以获得这些值   步骤本身?

$ awk 'FNR==1{split($0,a)}{s=0;for(i=1;i<=NF;i++)if(a[i]==$i)s+=$i=1;print $0,s}' f
1 1 1 1 1 1 6
1 1 B B B B 2
C A 1 1 1 1 4
1 1 D E F A 2
1 A A A A 1 2
1 1 1 B B B 3