awkfor循环未将数组索引设置为正确的值

我有这个小的地理位置数据集。

37.9636140,23.7261360
37.9440840,23.7001760
37.9637190,23.7258230
37.9901450,23.7298770

从一个随机位置。例如,37.97570, 23.66721
我需要使用 awk 创建一个 bash 命令,该命令返回具有简单欧几里德距离的距离。这是我使用的命令

awk -v OFMT=%.17g -F',' -v long=37.97570 -v lat=23.66721 '{for (i=1;i<=NR;i++) distances[i]=sqrt(($1 - long)^2 + ($2 - lat)^2 ); a[i]=$1; b[i]=$2} END {for (i in distances) print distances[i], a[i], b[i]}' filename

当我运行这个命令时,我得到了这个不正确的奇怪结果,有人可以向我解释我做错了什么吗?

? awk -v OFMT=%.17g -F',' -v long=37.97570 -v lat=23.66721 '{for (i=1;i<=NR;i++) distances[i]=sqrt(($1 - long)^2 + ($2 - lat)^2 ); a[i]=$1; b[i]=$2} END {for (i in distances) print distances[i], a[i], b[i]}' filename                     

44,746962127881936 37.9440840 23.7001760
44,746962127881936 37.9901450 23.7298770
44,746962127881936 37.9636140 23.7261360
44,746962127881936  
44,746962127881936 37.9637190 23.7258230

更新。

附加@jas 提供的命令,我od -c作为@mark-fuso suggetsted包含在内。

现在的问题是我从@jas 得到不同的结果

显示新问题的命令输出。

awk -v OFMT=%.17g -F, -v long=37.97570 -v lat=23.66721 '
{distance=sqrt(($1 - long)^2 + ($2 - lat)^2 ); print distance, $1, $2}
' file        
1,1820150904705098 37.9636140 23.7261360
1,1820150904705098 37.9440840 23.7001760
1,1820150904705098 37.9637190 23.7258230
1,1820150904705098 37.9901450 23.7298770

od -c 显示输入文件的内容。

od -c file
0000000   3   7   .   9   6   3   6   1   4   0   ,   2   3   .   7   2
0000020   6   1   3   6   0  n   3   7   .   9   4   4   0   8   4   0
0000040   ,   2   3   .   7   0   0   1   7   6   0  n   3   7   .   9
0000060   6   3   7   1   9   0   ,   2   3   .   7   2   5   8   2   3
0000100   0  n   3   7   .   9   9   0   1   4   5   0   ,   2   3   .
0000120   7   2   9   8   7   7   0  n
0000130

回答

虽然@jas 为这个问题提供了一个“修复”,但我想我会对 OP 的代码正在做什么发表一些评论......

一些基础...

  • awk程序({for (i=1;i<=NR;i++) ... ; b[i]=$2})抵靠输入文件的每行应用
  • 当从输入文件中读取每一行时,awk变量NR会跟踪行号(即NR=1第一行、NR=2第二行等)
  • 在最后一次通过for循环时,计数器(i在这种情况下)的值为NR+1(即,i++在最后一次通过循环时应用 ,从而离开i=NR+1
  • 除非对输入的每一行都有条件检查,否则awk程序将应用于输入文件中的每一行(包括空行——更多内容见下文)
  • for (i in distances)... 不保证按数字顺序处理数组索引

awk/for循环执行以下操作:

  • 对于第一个输入行 ( NR=1) 我们得到for (i=1;i<=1;i++) ...
  • 对于第二个输入行 ( NR=2) 我们得到for (i=1;i<=2;i++) ...
  • 对于第三个输入行 ( NR=3) 我们得到for (i=1;i<=3;i++) ...
  • 对于第 4 个输入行 ( NR=4) 我们得到for (i=1;i<=4;i++) ...

对于awk程序处理的每一行,都将覆盖distance[]数组中所有先前的条目;最终结果是最后一行 ( NR=4) 将在distance[]数组的所有 4 个条目中放置相同的值。

所述a[i]=$1; b[i]=$2阵列分配的范围之外发生for循环,使这些将每个输入行一次被分配(即,不会被覆盖),然而,该阵列分配正在与制成i=NR+1; 最终的结果是第1行(的内容NR=1)被存储在阵列的条目a[2]b[2],所述第二排(的内容NR=2)被存储在数组项a[3]a[3]

修改 OP 的代码print i, distances[i], a[i], b[i]}并针对我得到的 4 行输入文件运行:

1 0.064310270672728084                            # no data for 2nd/3rd columns because a[1] and b[1] are never set
2 0.064310270672728084 37.9636140 23.7261360      # 2nd/3rd columns are from 1st row of input
3 0.064310270672728084 37.9440840 23.7001760      # 2nd/3rd columns are from 2nd row of input
4 0.064310270672728084 37.9637190 23.7258230      # 2nd/3rd columns are from 3rd row of input

由此我们可以看到输出的第一列是相同的(即distance[1]=distance[2]=distance[3]=distance[4]),而第二列和第三列与输入列相同,只是它们“向下”移动了一行。

这给我们留下了两个悬而未决的问题......

  • 为什么 OP 显示 5 行输出?
  • 为什么第一列是垃圾44,746962127881936

我能够通过在输入文件的末尾添加一个空行来重现这个问题:

$ cat geo.dat
37.9636140,23.7261360
37.9440840,23.7001760
37.9637190,23.7258230
37.9901450,23.7298770
                           <<=== blank line !!

使用 OP 的awk代码生成以下内容:

44.746962127881936
44.746962127881936 37.9636140 23.7261360
44.746962127881936 37.9440840 23.7001760
44.746962127881936 37.9637190 23.7258230
44.746962127881936 37.9901450 23.7298770

注意事项

  • 此顺序与 OP 的示例输出不同,可能是由于 OP 的awk版本未按for (i in distances)...数字顺序处理;OP 可以尝试类似for (i=1;i<=NR;i++)...or for (i=1;i in distances; i++)...(尽管后者对于人口稀少的数组无法正常工作)
  • OPs 输出(在问题中;在@jas 答案的评论中)显示逗号 ( ,) 代替.第一列的句点 ( ),所以我猜 OP 的 env 使用的语言环境将逗号/句点切换为数千/decimal 分隔符(尽管输入数据基于“相反”语言环境)

请注意,我们终于看到了来自输入的第 4 行的数据(“向下移动”并显示在第 5 行),但第一列似乎是一个无意义的值……可以追溯到应用以下内容一个空行:

sqrt(($1 - long)^2     + ($2 - lat)^2     )
sqrt((   - long)^2     + (   - lat)^2     )  # empty line => $1 = $2 = undefined/empty
sqrt((   - 37.97570)^2 + (   - 23.66721^2 )  
sqrt( 1442.153790      +    560.136829    )
sqrt( 2002.290619                         )
44.746952...                                 # contents of 1st column 

为了“修复”这个问题,OP 可以a)从输入文件中删除空行或b)awk脚本添加一些逻辑以仅在输入行在字段 #1 和 #2 中具有(数字)值时才执行计算(即,$1并且$2不为空);由编码器决定应用多少验证(例如,字段是数字,是合法长/纬度值范围内的字段等)。


最后一个与设计相关的评论......如 jas 的回答所示,当所有所需的输出可以在处理每一行时“即时”生成时,不需要任何数组(这反过来减少了内存使用)输入文件。


以上是awkfor循环未将数组索引设置为正确的值的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>