AWK 中的数组需要澄清代码

AWK 中的数组需要澄清代码

我的任务是创建一个列表,其中原始数据中的代码之一将被从参考列表中读取的新代码替换。在这种情况下,只有一项更改,但可能会在需要时将更多更改添加到参考列表中。

参考列表(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[]BEGINgetline

执行操作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从主输入文件中读取这些值,例如100123。对于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是时100newval被赋值为10007if (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作为键(等于$1in 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

相关内容