使用awk逐行读取文件以替换某些行号中的字符

使用awk逐行读取文件以替换某些行号中的字符

我这里有这个脚本。它应该通过逐行读取文件 LineNumbers.file(每行包含一个行号)然后相应地用 ./ 替换 0/0 来运行循环。在 BEFORE_File.txt 中。它可以工作,但只需要文件 LineNumbers.file 的最后一行,而不是 >100 个条目。

我不确定我在这里做错了什么。你能帮我让 awk 逐行读取 LineNumbers.file 吗?

我已经可以使用它了sed -i "${line}s/0\/0/\.\/\./" "${myFileTmp}",但是对于我拥有的 >3GB 大文件来说,它真的很慢。所以我认为 awk 会是一个更快的选择。

非常感谢!

cat ./LineNumbers_TEMP/LineNumbers.file | while read line
do
myFileTmp=BEFORE_File.txt
awk -v var=${line} 'FNR==var { sub(/0\/0/, "\.\/\."); print }' "${myFileTmp}" > AFTER_File.txt
done

例如,文件如下所示:

cat ./LineNumbers_TEMP/LineNumbers.file
1
2
5

脚本之前的File.txt:

cat BEFORE_File.txt
0/0
0/0
0/1
0/1
0/0
0/0
0/0

运行脚本后文件应如下所示:

cat AFTER_File.txt
./.
./.
0/1
0/1
./.
0/0
0/0

目前我只得到这个:

./.

答案1

您的代码不起作用,因为对于从 读取的每个行号LineNumbers.file,您修改了原来的 BEFORE_File.txt并创建AFTER_File.txt.因此,finalAFTER_File.txt将仅包含对 中列出的最后一个行号所做的更改LineNumbers.file

此外,解析整个文件只是为了更改一行,然后执行多次,效率极低,当对行的修改相同时,效率会加倍。

最好先读取行号,然后一次性修改所有行:

awk 'FNR == NR { lineno[$1] = 1; next }
     (FNR in lineno) && $0 == "0/0" { $0 = "./." }
     { print }' LineNumbers.file BEFORE_File.txt >AFTER_File.txt

FNRNR是两个特殊变量,用于awk保存当前文件的当前记录号(默认为行号)以及迄今为止读取的所有记录(行)数。为了第一的输入文件时,这两个值将相同,当它们相同时,我们将行号作为键存储在关联数组中lineno,然后跳到下一行。

当它们不相同时,我们测试当前行号是否是数组中的键lineno,以及当前行是否另外等于0/0。如果是,则更改为./.。最后一个{ print }块输出第二个文件的所有行,无论是否修改。


一种完全不同的方法是sed使用创建一个sed脚本进行必要的更改。

给定行号n,sed 表达式将通过替换为来ns,^0/0$,./.,更改行。如果该行不完全是,则不会进行任何更改。我使用逗号作为命令的分隔符以避免n0/0./.0/0s///牙签倾斜综合征

我们要做的就是为每个行号创建类似的表达式n

sed 's#.*#&s,^0/0$,./.,#' LineNumbers.file

在这里,我使用#作为分隔符s///&命令的替换部分将替换为从输入文件中读取的行号。

对于给定的行号列表,这会生成

1s,^0/0$,./.,
2s,^0/0$,./.,
5s,^0/0$,./.,

我们可以简单地将其直接应用到我们的文件中:

sed 's#.*#&s,^0/0$,./.,#' LineNumbers.file | sed -f /dev/stdin BEFORE_File.txt >AFTER_File.txt

答案2

让我们看看这是否适合您:

awk '{ 
  if ( NR == FNR ) { 
    n[$1] = 0 
  } else { 
    if ( FNR in n ) { 
      gsub(/^0\/0$/, "./.", $0) 
    } 
    print 
  } 
}' LineNumbers.file BEFORE_File.txt > AFTER_File.txt

输出:

./.
./.
0/1
0/1
./.
0/0
0/0

答案3

鉴于您的输入实际上看起来像blabla 4858 ABC 0/0:4,3,2 0/1:4,3,2而不是您在问题中发布的示例,您所需要的只是:

awk 'NR==FNR{a[$1]; next} FNR in a{sub("0/0","./.")} 1' LineNumbers.file BEFORE_File.txt >AFTER_File.txt

对于您的下一个问题,请发布看起来像您真实输入的示例输入,以避免获得过于简单或比必要的复杂的答案和/或仅适用于您实际上没有的输入。

不要这样做,因为这在很多方面都是一种不好的方法,但仅供参考,如果您要使用像问题中那样的 shell 循环,那么您可以将其写为:

myFileTmp=$(mktemp)
cp BEFORE_File.txt AFTER_File.txt
while IFS= read -r line
do
    awk -v var="${line}" '
        FNR==var { sub("0/0", "./.") } { print }
    ' AFTER_File.txt > "$myFileTmp" &&
    mv "$myFileTmp" AFTER_File.txt
done < LineNumbers.file

另外,您问题中的脚本 -"\.\/\."您的 gsub() 中是一个字符串。您不需要在字符串中转义正则表达式元字符,只需在正则表达式中转义即可。同上/。您需要写的就是"./.".看为什么使用 shell 循环处理文本被认为是不好的做法?,http://porkmail.org/era/unix/award.html, 和https://mywiki.wooledge.org/Quotes除了您遇到的问题之外,您的脚本还存在一些其他问题。

答案4

可以通过 getline 将带有行号的文件中的行直接读取到 awk 中的变量中(假设行号已排序):

getline var <"filename"

整个脚本将是对 awk 的一次调用,如下所示:

awk -v f1='./LineNumbers.file' '
       NR >var+0 {    rc=getline var <f1;
                      if(rc<0){  stderr = "cat 1>&2";
                                 print "error reading",f1 | stderr;
                                 close(stderr);
                                 exit 1
                              }
                 }
       NR==var+0 {    sub(/0\/0/,"./.")
                 }
     1' BEFORE_File.txt

当然,将输出重定向到您喜欢的任何文件。

相关内容