![如果所有行都是 0、NA 和 1,则替换所有 0](https://linux22.com/image/176544/%E5%A6%82%E6%9E%9C%E6%89%80%E6%9C%89%E8%A1%8C%E9%83%BD%E6%98%AF%200%E3%80%81NA%20%E5%92%8C%201%EF%BC%8C%E5%88%99%E6%9B%BF%E6%8D%A2%E6%89%80%E6%9C%89%200.png)
我的问题是这样的:
我想检查从第四列到最后的所有行字段值是否为仅有的0(格式为0.00
)、1(格式为1.00
) 或NA
,如果是,则将0
值替换为0.001
。
例如这样的行:
MA_10 49498 49499 NA NA NA NA 0.00 NA 1.00 NA NA NA NA 1.00 NA NA NA NA 1.00 NA NA NA NA NA NA 1.00 NA NA NA NA
这是预期的输出:
MA_10 49498 49499 NA NA NA NA 0.001 NA 1.00 NA NA NA NA 1.00 NA NA NA NA 1.00 NA NA NA NA NA NA 1.00 NA NA NA NA
但它不应该像这样接触一行:
MA_10 49499 49500 NA NA NA NA 0.00 NA 0.50 NA NA NA NA 1.00 NA NA NA NA 1.00 NA NA NA NA NA NA 1.00 NA NA NA NA
因为它的值不同于 NA、0 和 1,即 0.50。
我这样做了 smt 但不会工作,因为它也会取代 1。
#!/bin/bash -ue
BEGIN { OFS = FS = "\t" }
NR != 1 {
for (i = 4; i <= NF; ++i) {
if ($i = "0" || $i= "1") {
$i = "0.01";
}
}
}
{ print $0 }
提前致谢!
答案1
01.awk
:
BEGIN{FS=OFS="\t"}
skip=0
{
for(i=4;i<=NF;i++){
if($i !~ /NA/ && $i!=0 && $i!=1){
skip=1
break
}
}
}
!skip{gsub(/0\.0+/,"0.001")}
1
for 循环尝试在从第四列开始的一行中查找非 NA、非零和非 1 字段。如果找到,skip
则设置为 1,因此该行
!skip{gsub(/0\.0+/,"0.001")}
不被执行并且0.00
不会成为0.001
。
执行脚本
awk -f 01.awk inputfile
PS:#!/bin/bash -ue
您的尝试实际上没有意义,因为您编写了 awk 脚本,而不是 bash 脚本。
答案2
您可以尝试以下操作(为了便于阅读,我使用“行继续”将其分成几行,并在行尾添加反斜杠):
awk -F'\t' -v OFS='\t' '{delete a; nzero=0;\
for (i=4;i<=NF;i++){\
if ($i==0) a[++nzero]=i;\
if ($i!=0 && $i!=1 && $i!="NA") {print; next;}\
}\
for (i=1;i<=nzero;i++) {$a[i]=0.001;}; print;}' input.txt
- 这将解析所有行以查看哪些字段(从第 4 个字段开始)的数值为零,并将字段编号存储在数组中,
a
并将此类字段的数量存储在nzero
. - 同时,它会检查是否有任何检查字段不是匹配 0、1 或“NA”。如果是这样,该行将“按原样”打印,并跳到下一行执行。
- 如果没有找到“非法”字段,
a
则解析数组,并将存储在那里的所有字段号替换为0.001
.
delete a
请注意,清除数组的语法需要 GNU Awk。对于其他实现,请split("",a)
改为使用。
与 Awk 脚本相同(我们称之为replace.awk
):
#!/bin/awk -f
BEGIN{FS=OFS="\t"}
{
delete a;
nzero=0;
for (i=4;i<=NF;i++)
{
if ($i==0) a[++nzero]=i;
if ($i!=0 && $i!=1 && $i!="NA")
{
print;
next;
}
}
for (i=1;i<=nzero;i++) $a[i]=0.001;
print;
}
用于
awk -f replace.awk input.txt
答案3
这是awk
我们通过正则表达式选择要修改的记录的另一种方法。
$ awk '/^([\t]*[^\t]+){3}([\t]+([01][.]00|NA))+$/ && gsub(/0\.00/, "0.001") || 1' file
正则表达式解码:
^([\t]*[^\t]+){3}
遍历当前记录的前三个字段(字段以制表符分隔)。
([\t]+([01][.]00|NA))
是从第四个开始的“好”场的形式。
在好字段后面放置 a+
并一直到记录末尾,这意味着第三个字段之后的所有字段都是“好”。这意味着这是我们要修改的行。
会将gsub
“0.00”替换为“0.001”
===============================
下面是动态生成正则表达式并进行更改的 awk 代码。
$ gudFld="[01][.]00|NA" \
awk '
function enc(arg) {
return "(" arg ")"
}
BEGIN {
sp = "\t"
s = "[" sp "]"
S = "[^" sp "]"
f_ = s"*" S"+"; f = enc(f_)
f3 = f"{3}"
e = enc( ENVIRON["gudFld"])
g_ = s"+" e; g = enc(g_)
pat = "^" f3 g"+" "$"
}
$0 ~ pat && gsub(/0.00/, "&1") || 1
' file
如果我们想要偏执并考虑到该模式0.00
可能出现在前三个字段中的可能性,那么我们可以用以下内容替换最后一行。在这里,我们仅在第 4 个字段开始运行 gsub。
$0 ~ pat {
match($0, f3)
f123 = substr($0, 1, RLENGTH)
f4_end = substr($0, RLENGTH+1)
gsub(/0.00/, "&1", f4_end)
$0 = f123 f4_end
}1