BASH - 使用 Loop 和 If 语句从唯一字段中的多个字段汇总信息
BASH - Summarising information from several fields in unique field using Loop and If statements
我有以下制表符分隔的文件:
A1 A1 0 0 2 1 1 1 1 1 1 1 2 1 1 1
A2 A2 0 0 2 1 1 1 1 1 1 1 1 1 1 1
A3 A3 0 0 2 2 1 1 2 2 1 1 1 1 1 1
A5 A5 0 0 2 2 1 1 1 1 1 1 1 2 1 1
我们的想法是总结第 7 列(包括在内)和添加到文件末尾的新列末尾之间的信息。
这样做的规则是:
如果行中(第 7 列和末尾之间)“2”的总数为 0:将“1 1”添加到新的最后一列
如果行中“2”的总数(第 7 列和末尾之间)为 1:将“1 2”添加到新的最后一列
如果行中(第 7 列和末尾之间)“2”的总数为 2 或更多:添加“2 2”到新的最后一列
我开始使用以下命令提取我想要处理的列:
awk '{for (i = 7; i <= NF; i++) printf $i " "; print ""}' myfile.ped > tmp_myfile.txt
然后我计算每行中出现的次数:
sed 's/[^2]//g' tmp_myfile.txtt | awk '{print NR, length }' >
tmp_occurences.txt
输出:
1 1
2 0
3 2
4 1
然后我的想法是编写一个 for 循环,循环遍历各行以添加新的摘要列。
根据我在这里找到的内容,我正在考虑这种 结构 :http://www.thegeekstuff.com/2010/06/bash-if-statement-examples:
while read line ;
do
set $line
If [""==0]
then
=="1 1"
elif [""==1 ]
then
=="1 2”
elif ["">=2 ]
then
==“2 2”
else
print ["error"]
fi
done < tmp_occurences.txt
但是我被困在这里了。我是否必须在开始循环之前创建新列?我正朝着正确的方向前进吗?
理想情况下,最终输出(合并初始文件的前 6 列和摘要列之后)将是:
A1 A1 0 0 2 1 1 2
A2 A2 0 0 2 1 1 1
A3 A3 0 0 2 2 2 2
A5 A5 0 0 2 2 1 2
感谢您的帮助!
我们可以通过使用 gensub()
和捕获组来保持格式:我们捕获前 6 个字段并替换为它们 + 计算值:
awk '{for (i=7; i<=NF; i++) {
if ($i==2)
twos+=1 # count number of 2's from 7th to last field
}
f7=1; f8=0 # set 7th and 8th fields's default value
if (twos)
f8=2 # set 8th = 2 if sum is > 0
if (twos>1)
f7=2 # set 7th = 2 if sum is > 1
[=10=]=gensub(/^((\S+\s*){6}).*/,"\1 " f7 FS f8, 1) # perform the replacement
twos=0 # reset counter
}1' file
单线:
$ awk '{for (i=7; i<=NF; i++) {if ($i==2) twos+=1} f7=1; f8=0; if (twos) f8=2; if (twos>1) f7=2; [=11=]=gensub(/^((\S+\s*){6}).*/,"\1 " f7 FS f8,1); twos=0}1' a
A1 A1 0 0 2 1 1 2
A2 A2 0 0 2 1 1 0
A3 A3 0 0 2 2 2 2
A5 A5 0 0 2 2 1 2
使用 gnu-awk 你可以做到:
awk -v OFS='\t' '{
c=0;
for (i=7; i<=NF; i++)
if ($i==2)
c++
if (c==0)
s="1 1"
else if (c==1)
s="1 2"
else
s="2 2"
NF=6
print [=10=], s
}' file
A1 A1 0 0 2 1 1 2
A2 A2 0 0 2 1 1 1
A3 A3 0 0 2 2 2 2
A5 A5 0 0 2 2 1 2
PS:如果不使用 gnu-awk,您可以使用:
awk -v OFS='\t' '{c=0; for (i=7; i<=NF; i++) {if ($i==2) c++; $i=""} if (c==0) s="1 1"; else if (c==1) s="1 2"; else s="2 2"; NF=6; print [=11=], s}' file
$ cat > test.awk
{
for(i=1;i<=NF;i++) { # for every field
if(i<7)
printf "%s%s", $i,OFS # only output the first 6
else a[$i]++ # count the values of the of the fields
}
print (a[2]>1?"2 2":(a[2]==1?"1 2":"1 1")) # output logic
delete a # reset a for next record
}
$ awk -f test.awk test
A1 A1 0 0 2 1 1 2
A2 A2 0 0 2 1 1 1
A3 A3 0 0 2 2 2 2
A5 A5 0 0 2 2 1 2
借鉴@anubhava 上面的解决方案的一些想法:
$ cat > another.awk
{
for(i=7;i<=NF;i++)
a[$i]++ # count 2s
NF=6 # truncate [=11=]
print [=11=] OFS (a[2]<2?"1 "(a[2]?"2":"1"):"2 2") # print [=11=] AND 1 AND 1 OR 2 OR 2 AND 2
delete a # reset a for next record
}
使用 GNU awk 匹配第三个参数():
$ awk '{match([=10=],/((\S+\s+){6})(.*)/,a); c=gsub(2,2,a[3]); print a[1] (c>1?2:1), (c>0?2:1)}' file
A1 A1 0 0 2 1 1 2
A2 A2 0 0 2 1 1 1
A3 A3 0 0 2 2 2 2
A5 A5 0 0 2 2 1 2
对于其他 awk,您需要将 \S/\s
替换为 [^[:space:]]/[[:space:]]
并使用 substr()
而不是 a[]
。
我有以下制表符分隔的文件:
A1 A1 0 0 2 1 1 1 1 1 1 1 2 1 1 1
A2 A2 0 0 2 1 1 1 1 1 1 1 1 1 1 1
A3 A3 0 0 2 2 1 1 2 2 1 1 1 1 1 1
A5 A5 0 0 2 2 1 1 1 1 1 1 1 2 1 1
我们的想法是总结第 7 列(包括在内)和添加到文件末尾的新列末尾之间的信息。
这样做的规则是:
如果行中(第 7 列和末尾之间)“2”的总数为 0:将“1 1”添加到新的最后一列
如果行中“2”的总数(第 7 列和末尾之间)为 1:将“1 2”添加到新的最后一列
如果行中(第 7 列和末尾之间)“2”的总数为 2 或更多:添加“2 2”到新的最后一列
我开始使用以下命令提取我想要处理的列:
awk '{for (i = 7; i <= NF; i++) printf $i " "; print ""}' myfile.ped > tmp_myfile.txt
然后我计算每行中出现的次数:
sed 's/[^2]//g' tmp_myfile.txtt | awk '{print NR, length }' > tmp_occurences.txt
输出:
1 1
2 0
3 2
4 1
然后我的想法是编写一个 for 循环,循环遍历各行以添加新的摘要列。 根据我在这里找到的内容,我正在考虑这种 结构 :http://www.thegeekstuff.com/2010/06/bash-if-statement-examples:
while read line ;
do
set $line
If [""==0]
then
=="1 1"
elif [""==1 ]
then
=="1 2”
elif ["">=2 ]
then
==“2 2”
else
print ["error"]
fi
done < tmp_occurences.txt
但是我被困在这里了。我是否必须在开始循环之前创建新列?我正朝着正确的方向前进吗?
理想情况下,最终输出(合并初始文件的前 6 列和摘要列之后)将是:
A1 A1 0 0 2 1 1 2
A2 A2 0 0 2 1 1 1
A3 A3 0 0 2 2 2 2
A5 A5 0 0 2 2 1 2
感谢您的帮助!
我们可以通过使用 gensub()
和捕获组来保持格式:我们捕获前 6 个字段并替换为它们 + 计算值:
awk '{for (i=7; i<=NF; i++) {
if ($i==2)
twos+=1 # count number of 2's from 7th to last field
}
f7=1; f8=0 # set 7th and 8th fields's default value
if (twos)
f8=2 # set 8th = 2 if sum is > 0
if (twos>1)
f7=2 # set 7th = 2 if sum is > 1
[=10=]=gensub(/^((\S+\s*){6}).*/,"\1 " f7 FS f8, 1) # perform the replacement
twos=0 # reset counter
}1' file
单线:
$ awk '{for (i=7; i<=NF; i++) {if ($i==2) twos+=1} f7=1; f8=0; if (twos) f8=2; if (twos>1) f7=2; [=11=]=gensub(/^((\S+\s*){6}).*/,"\1 " f7 FS f8,1); twos=0}1' a
A1 A1 0 0 2 1 1 2
A2 A2 0 0 2 1 1 0
A3 A3 0 0 2 2 2 2
A5 A5 0 0 2 2 1 2
使用 gnu-awk 你可以做到:
awk -v OFS='\t' '{
c=0;
for (i=7; i<=NF; i++)
if ($i==2)
c++
if (c==0)
s="1 1"
else if (c==1)
s="1 2"
else
s="2 2"
NF=6
print [=10=], s
}' file
A1 A1 0 0 2 1 1 2
A2 A2 0 0 2 1 1 1
A3 A3 0 0 2 2 2 2
A5 A5 0 0 2 2 1 2
PS:如果不使用 gnu-awk,您可以使用:
awk -v OFS='\t' '{c=0; for (i=7; i<=NF; i++) {if ($i==2) c++; $i=""} if (c==0) s="1 1"; else if (c==1) s="1 2"; else s="2 2"; NF=6; print [=11=], s}' file
$ cat > test.awk
{
for(i=1;i<=NF;i++) { # for every field
if(i<7)
printf "%s%s", $i,OFS # only output the first 6
else a[$i]++ # count the values of the of the fields
}
print (a[2]>1?"2 2":(a[2]==1?"1 2":"1 1")) # output logic
delete a # reset a for next record
}
$ awk -f test.awk test
A1 A1 0 0 2 1 1 2
A2 A2 0 0 2 1 1 1
A3 A3 0 0 2 2 2 2
A5 A5 0 0 2 2 1 2
借鉴@anubhava 上面的解决方案的一些想法:
$ cat > another.awk
{
for(i=7;i<=NF;i++)
a[$i]++ # count 2s
NF=6 # truncate [=11=]
print [=11=] OFS (a[2]<2?"1 "(a[2]?"2":"1"):"2 2") # print [=11=] AND 1 AND 1 OR 2 OR 2 AND 2
delete a # reset a for next record
}
使用 GNU awk 匹配第三个参数():
$ awk '{match([=10=],/((\S+\s+){6})(.*)/,a); c=gsub(2,2,a[3]); print a[1] (c>1?2:1), (c>0?2:1)}' file
A1 A1 0 0 2 1 1 2
A2 A2 0 0 2 1 1 1
A3 A3 0 0 2 2 2 2
A5 A5 0 0 2 2 1 2
对于其他 awk,您需要将 \S/\s
替换为 [^[:space:]]/[[:space:]]
并使用 substr()
而不是 a[]
。