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

Awk 根据字符串的出现情况对列重新编号,awk,sed,seq,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

很抱歉,我对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

非常感谢您的帮助。

编辑:这是一个使用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
字段数。如果 省略r,而使用FS。首先清除阵列a和SEP。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
第三种解决方案:若要在行的最后添加/插入新列,请执行以下操作

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

使用简单的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

相同的解决方案,同时保留初始场位置

$ 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

请注意,这假设第一个字段的值与第二个字段的值不重叠,如图所示,否则您需要进行保护,以仅保留对第二个字段的更改。对于第二个字段,可以很容易地通过在匹配和替换值前加上单个空格来完成

第三个参数使用GNU awk进行匹配()和
\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元字符或任何其他内容,上述操作仍适用于所有输入

以上内容用于修改第二个字段。通常,要修改n=4的第n个字段,例如,硬编码将是:

$ 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

尽管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
解释:

$ 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

这太棒了!非常感谢。现在,如果我需要将该列更改为第4列,我只需将$2的占用率替换为$4?@WagnerAG,你的意思是2和4应该具有相同的值?@WagnerAG,请检查我的编辑解决方案,并让我知道这些是否有帮助。非常感谢你的帮助@RavinderSingh13我编辑了上面的帖子,以反映我的想法mean@WagnerAG,请检查我的编辑解决方案,现在让我知道?这是一个!非常感谢。然而,我有一个问题;在我使用的实际文件中,有一些列具有不同的间距,使用这些列后,格式会发生变化。因此,如果上面的文件还有5个空格的话,它现在变成了单空格。非常感谢!这为我节省了大量时间。@WagnerAG希望你能用节省的时间做些有用的事情:)。。。。例如阅读Arnold Robbins的第四版《有效的AWK编程》:-)@karakfa如果我这样做,那么%02d格式将导致
c01
在C和01OK之间有一个空格,您必须用数字替换两个空格<代码>sprintf需要相应更改。是。。我想这里不需要3美元。。让我检查一下并更新一下,我们又找到了一种方法
[^^]\S+
$6=int(++a[$5]/10)$7=a[$5]%10