我这里有这个脚本。它应该通过逐行读取文件 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
FNR
和NR
是两个特殊变量,用于awk
保存当前文件的当前记录号(默认为行号)以及迄今为止读取的所有记录(行)数。为了第一的输入文件时,这两个值将相同,当它们相同时,我们将行号作为键存储在关联数组中lineno
,然后跳到下一行。
当它们不相同时,我们测试当前行号是否是数组中的键lineno
,以及当前行是否另外等于0/0
。如果是,则更改为./.
。最后一个{ print }
块输出第二个文件的所有行,无论是否修改。
一种完全不同的方法是sed
使用创建一个sed
脚本进行必要的更改。
给定行号n
,sed 表达式将通过替换为来ns,^0/0$,./.,
更改行。如果该行不完全是,则不会进行任何更改。我使用逗号作为命令的分隔符以避免n
0/0
./.
0/0
s///
牙签倾斜综合征。
我们要做的就是为每个行号创建类似的表达式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
当然,将输出重定向到您喜欢的任何文件。