我的任务是创建一个列表,其中原始数据中的代码之一将被从参考列表中读取的新代码替换。在这种情况下,只有一项更改,但可能会在需要时将更多更改添加到参考列表中。
参考列表(mycodes)具有以下值:
100,100007
数据是三位数代码的流,但 100 的代码应与流的其余部分一起作为五位数代码写出。我使用了 AWK 程序,如下所示;-
BEGIN{
FS=","
reffile="mycodes"
while(getline<reffile>0) {ref[$1]=$2}
}
{
val=$1
newval=ref[val]
if (newval in ref) { outval=val}
else {outval=newval}
print outval
}
输入数据文件包含以下值:
100
101
120
130
100
程序运行时确实产生了正确的输出
100007
101
120
130
10007
但是,仅当参考文件中第一个条目之后有空格时,它才有效。如果缺少空格,则程序不会生成除 100007 之外的任何内容作为输出。
我不明白这个 AWK 程序的逻辑到底发生了什么,我想知道是否有人可以帮助解释它 - 特别是关于if (newval in ref)
.
答案1
如果您要使用getline
来填充$0
,那么这里是如何做到这一点(请参阅http://awk.freeshell.org/AllAboutGetline):
while ( (getline < reffile) > 0 ) {
ref[$1] = $2
}
脚本的其余部分应该只有一行:
{ print ( $1 in ref ? ref[$1] : $1 ) }
所以整个脚本是:
BEGIN {
FS = ","
reffile = "mycodes"
while ( (getline < reffile) > 0 ) {
ref[$1] = $2
}
}
{ print ( $1 in ref ? ref[$1] : $1 ) }
我假设您有充分的理由创建一个变量来保存文件名"mycodes"
,并且不想将其作为参数传递。
或者你可以这样做:
BEGIN { FS = "," }
NR==FNR { ref[$1] = $2; next }
{ print ( $1 in ref ? ref[$1] : $1 ) }
并将其称为。这比使用循环填充awk 'script' mycodes file
效率略低,但对于大多数应用程序来说,这不太可能成为问题,并且显然使用更简洁、更难出错的代码。refs[]
BEGIN
getline
执行操作print ( $1 in ref ? ref[$1] : $1 )
比执行或类似操作更有效,if ($1 in ref) $1=ref; print $1
因为它不会强制 awk 重建当前记录,但同样,不太可能有意义。
话虽如此,尽管它有问题,但您现有的脚本可能不会以您所描述的方式失败,而您真正的问题是 DOS 行结尾(请参阅https://stackoverflow.com/questions/45772525/why-does-my-tool-output-overwrite-itself-and-how-do-i-fix-it?)。
答案2
val=$1
newval=ref[val]
if (newval in ref) { outval=val }
else { outval=newval }
因此,您val
从主输入文件中读取这些值,例如100
或123
。对于ref
包含像 之类的对ref[100]=100007
,您可能想要检查是否val
存在作为键ref
(即元素是否ref[val]
存在)。如果ref[val]
作为键(或ref[ref[val]]
元素)存在,则不会。
所以就这样做吧if (val in ref)
。
那么,如果它做存在,您可能想使用从那里找到的值(您在newval
),并且如果不是,然后是旧值 ( val
)。所以做到这一点
val=$1
newval=ref[val]
if (val in ref) { outval=newval }
else { outval=val }
除了@αГsнιη提到的,现在你遇到了分配的问题newval=ref[val]
创造 ref[val]
使用空字符串作为值(如果它尚不存在),所以我们需要对此做一些事情。
要么完全删除并仅在测试后newval
使用:ref[val]
val=$1
# newval=ref[val] # remove this line
if (val in ref) { outval=ref[val] }
else { outval=val }
或者,将其留在那里,然后针对空字符串进行测试:
val=$1
newval=ref[val]
if (newval != "") { outval=newval }
else { outval=val }
mycodes
如果第二个字段中可以包含空值,则它们之间的差异是有意义的。
答案3
给定
reffile="mycodes"
while(getline<reffile>0) {ref[$1]=$2}
中的空行mycodes
创建一个元素,ref
其值为空字符串,其索引也为空字符串。
然后,在处理输入流时,
newval=ref[val]
newval
每次$1
( 100
, 101
, 120
, 130
) 都被分配空字符串100
,它是元素不为空的唯一索引ref
(索引是初始的100
和""
(空),加上每个 后续创建的空元素的索引newval=ref[val]
,即101
, 120
, 130
) 。在这些情况下,由于空字符串是 的索引之一ref
,因此
if (newval in ref) { outval=val}
else {outval=newval}
if (newval in ref)
成功并且打印的值是输入流(val
、 from val=$1
)中的当前值。
另一方面,如果ref
没有以空字符串作为索引的元素(当 中没有空行时会发生这种情况mycodes
),则空字符串if (newval in ref)
每次都会失败。newval
然后newval
(空行),被打印。
在这两种情况下,当$1
是时100
,newval
被赋值为10007
;if (newval in ref)
失败并newval
因此被打印。
请注意,即使文件中没有空行,输入流中的空行也可能会触发相同的令人费解的行为mycodes
。
假设,在你的问题中,一个或多个10007
,100007
和 “五位数代码”是一个拼写错误,并且你实际上总是意味着10007
(五位数),我会将你的 AWK 程序重写为:
awk -v FS=, '
NR == FNR {
ref[$1] = $2
next
}
($1 in ref) {
$1 = ref[$1]
}
1
' ./mycodes -
或者
awk '
BEGIN {
FS=","
while ( (getline < "mycodes") > 0 )
ref[$1] = $2
}
($1 in ref) {
$1 = ref[$1]
}
1
' -
(谢谢αГsнιm指出newval=ref[val]
实际上添加val
为索引ref
,因此这($1 in ref)
是在数组中搜索匹配索引的更安全的方法)。
答案4
主要问题是在这一行newval=ref[val]
,其中变量val
作为键(等于$1
in val=$1
),这实际上意味着newval=ref[$1]
,在这一行中,如果该键存在,则newval
变量的内容设置为 ref[] 数组中的值,ref[val]
否则设置为空值;这样做,即newval=ref[val]
如果在数组中找不到该键,该键将被添加到具有空值的数组中,当您不希望更改数组大小/索引时,您可能会遇到一些未来的问题,或者它可能如果该文件很大,则超出可用内存或显着减慢脚本速度。
...然后在 if-else 语句中并且您正在测试newval
,它总是运行else
部分。
if (newval in ref) {
outval = val; ## this section never runs
} else {
outval = newval ## this sections runs for every tests
}
因此,只要该行中newval=ref[val]
存在键,outval
就会采用该键的值,否则将设置为空值。通过打印 in print outval
,如果在 ref[] 数组中找到这些键的值,则输出这些键的值,否则输出空行。
简单的修复(无需额外的改进),将该行更改为:
newval = (val in ref)?ref[val]:val
你的命令都可以写成如下:
$ awk 'BEGIN{ FS="," }
NR==FNR { ref[$1]=$2; next }
($1 in ref){ $1=ref[$1] }1' reference infile
100007
101
120
130
100007
$ cat reference
100,100007
$ cat infile
100
101
120
130
100