根据出现的字符串对列重新编号

时间:2018-12-17 16:56:16

标签: awk sed seq

我是Linux的新手,

我有一个这样的文件:

1   C   foo   C     bar
2   C   foo   C     bar
3   C   foo   C     bar
4   H   foo   H     bar
5   H   foo   H     bar
6   O   foo   O     bar

我需要使它成为:

1   C01 foo   C     bar
2   C02 foo   C     bar
3   C03 foo   C     bar
4   H01 foo   H     bar
5   H02 foo   H     bar
6   O01 foo   O     bar

**不幸的是,必须保持foo和C之间的间距以及C和bar之间的间距。

我以分段方式进行了尝试,在其中我拉出了包含不同标识符C,H和O的行,并将它们放置在临时文件中。然后,我尝试按出现顺序对其进行排序,然后将原始文件重新拼接在一起。

    #!/bin/bash

    sed -i -e "/ C /w temp1.txt" -e "//d" File.txt
    sed -i -e "/ H /w temp2.txt" -e "//d" File.txt
    sed -i -e "/ O /w temp3.txt" -e "//d" File.txt


    `awk -i '{print NR $2}' temp1.txt
    awk -i '{print NR $2}' temp2.txt
    awk -i '{print NR $2}' temp3.txt

    cat temp1.txt >> File.txt
    cat temp2.txt >> File.txt
    cat temp3.txt >> File.txt

但是我很确定我的语法很糟糕,因为我真的只熟悉sed而不是awk。

任何帮助将不胜感激,谢谢。

6 个答案:

答案 0 :(得分:3)

编辑: 这是使用GNU awk的解决方案,它保留了实际的空间。如果您的split支持4个参数。阅读手册页后,我明白了,即使我很高兴找到它,也会有所帮助。

awk '
{
  n=split($0,array," ",b)
  array[2]=sprintf("%s%02d",array[2],++a[array[2]])
  line=b[0]
  for(i=1;i<=n;i++){
    line=(line array[i] b[i])
  }
  print line
}'  Input_file
1   C01   foo   C     bar
2   C02   foo   C     bar
3   C03   foo   C     bar
4   H01   foo   H     bar
5   H02   foo   H     bar
6   O01   foo   O     bar

关于GNU split手册页中的awk,其中包含4个参数:

   split(s, a [, r [, seps] ])
                           Split the string s into the array a and the separators array seps on the regular expression r, and return the
     

字段数。如果                                  省略r,使用FS代替。数组a和seps首先被清除。 seps [i]是字段   分隔符由r匹配                                  a [i]和a [i + 1]。如果r是单个空格,则s中的前导空格将进入额外的数组元素   seps [0]和结尾的白色-                                  空格进入额外的数组元素seps [n],其中n是split(s,a,r,seps)的返回值。   分割行为相同                                  进行字段拆分,如上所述。



第一个解决方案: ,请您尝试一下,

awk '{$2=sprintf("%s%02d",$2,++a[$2])} 1' Input_file

输出如下。

1 C01 bar C
2 C02 bar C
3 C03 bar C
4 H01 bar H
5 H02 bar H
6 O01 bar O

第二个解决方案: 如果您想同时在$ 2和$ 4中使用值,请执行以下操作。

awk '{$2=$4=sprintf("%s%02d",$2,++a[$2])} 1'  Input_file
1 C01 bar C01
2 C02 bar C02
3 C03 bar C03
4 H01 bar H01
5 H02 bar H02
6 O01 bar O01

第三种解决方案: :如果要在行的最后添加/插入新列,请执行以下操作。

awk '{$(NF+1)=sprintf("%s%02d",$2,++a[$2])} 1'  Input_file
1 C bar C C01
2 C bar C C02
3 C bar C C03
4 H bar H H01
5 H bar H H02
6 O bar O O01

答案 1 :(得分:3)

在保留初始场位置的同时采用相同的解决方案

$ awk '{r=sprintf("%02d",++a[$2]); sub($2"  ",$2r)}1' file

1   C01 foo   C     bar
2   C02 foo   C     bar
3   C03 foo   C     bar
4   H01 foo   H     bar
5   H02 foo   H     bar
6   O01 foo   O     bar

请注意,这假定第一个字段值与第二个字段值不重叠,如图所示,否则您需要注意仅将更改保留在第二个字段上。对于第二个字段,可以通过使用单个空格为匹配和替换值添加前缀来轻松完成。

答案 2 :(得分:3)

使用GNU awk作为match()的第三个参数,而\S/\s的缩写为[^[:space]:]]/[[:space:]]

$ awk 'match($0,/(\S+\s+)(\S+)(.*)/,a){ printf "%s%s%02d%s\n", a[1], a[2], ++cnt[a[2]], a[3] }' file
1   C01   foo   C     bar
2   C02   foo   C     bar
3   C03   foo   C     bar
4   H01   foo   H     bar
5   H02   foo   H     bar
6   O01   foo   O     bar

即使前面的字段与目标字段具有相同的值,或者目标字段包含RE元字符或其他任何内容,以上内容也适用于 ALL 输入。

上面是修改第二个字段。通常,将第n个字段修改为n = 4,例如,硬编码为:

$ awk 'match($0,/((\S+\s+){3})(\S+)(.*)/,a){ printf "%s%s%02d%s\n", a[1], a[3], ++cnt[a[3]], a[4]}' file
1   C   foo   C01     bar
2   C   foo   C02     bar
3   C   foo   C03     bar
4   H   foo   H01     bar
5   H   foo   H02     bar
6   O   foo   O01     bar

,如果它是作为参数而不是硬编码传递的:

$ awk -v n=4 'match($0,"((\\S+\\s+){"n-1"})(\\S+)(.*)",a){ printf "%s%s%02d%s\n", a[1], a[3], ++cnt[a[3]], a[4]}' file
1   C   foo   C01     bar
2   C   foo   C02     bar
3   C   foo   C03     bar
4   H   foo   H01     bar
5   H   foo   H02     bar
6   O   foo   O01     bar

答案 3 :(得分:1)

使用简单的awk脚本:

$ awk '{$2=sprintf("%s%02d",$2,++a[$2]);}1' file
1 C01 foo C
2 C02 foo C
3 C03 foo C
4 H01 foo H
5 H02 foo H
6 O01 foo O

答案 4 :(得分:0)

尽管未标记Perl,但似乎很适合这些情况。如果您正在考虑使用Perl,请检查一下。

> cat wagner.txt
1   C   foo   C     bar
2   C   foo   C     bar
3   C   foo   C     bar
4   H   foo   H     bar
5   H   foo   H     bar
6   O   foo   O     bar
> perl -pe 's/(\s+)(\S+)(\s+)/sprintf("%s%s%02d%s",$1,$2,++$kv{$2},$3)/e ' wagner.txt
1   C01   foo   C     bar
2   C02   foo   C     bar
3   C03   foo   C     bar
4   H01   foo   H     bar
5   H02   foo   H     bar
6   O01   foo   O     bar
>

感谢Karakfa,删除$ 3可以进一步缩短答案

>  perl -pe 's/(\s+)(\S+)/sprintf("%s%s%02d",$1,$2,++$kv{$2})/e ' wagner.txt
1   C01   foo   C     bar
2   C02   foo   C     bar
3   C03   foo   C     bar
4   H01   foo   H     bar
5   H02   foo   H     bar
6   O01   foo   O     bar
>

通过进一步删除另外一个组的另一种方法

> perl -pe 's/([^^]\S+)/sprintf("%s%02d",$1,++$kv{$1})/e ' wagner.txt
1   C01   foo   C     bar
2   C02   foo   C     bar
3   C03   foo   C     bar
4   H01   foo   H     bar
5   H02   foo   H     bar
6   O01   foo   O     bar
>

或使用环视

perl -pe 's/([^?!]\S+)/sprintf("%s%02d",$1,++$kv{$1})/e ' wagner.txt

答案 5 :(得分:0)

$ awk 'BEGIN{FS=OFS=""}{$6="";$7=((b=++a[$5])>9?"":0) b}1' file file file file
1   C01 foo   C     bar
2   C02 foo   C     bar
3   C03 foo   C     bar
4   H01 foo   H     bar
...
6   O03 foo   O     bar
1   C10 foo   C     bar
2   C11 foo   C     bar

解释:

$ awk 'BEGIN {
    FS=OFS=""                 # empty field separators
}
{
    $6=""                     # null $6
    $7=((b=++a[$5])>9?"":0) b # $7 carries the count, with leading 0 if below 10
}1' file