awk 混淆了两个包含数字的不同字符串

Posted

技术标签:

【中文标题】awk 混淆了两个包含数字的不同字符串【英文标题】:awk confuses two distinct strings that contain numbers 【发布时间】:2021-07-27 21:38:31 【问题描述】:

我使用 awk 脚本

 BEGIN  
   FS=";";
   id=0; 
 

 NR >= 1 
  if($2 == id) 
    print "Old person", $2, id;
   else 
    id = $2;
    print "New person ", id
  

在以下文件中

1220100120160850207;12201001201608502;114.384332;59475;16
1220100120160850208;12201001201608502;114.384332;59475;16
1220100120160850301;12201001201608503;68.3642617;59475;07
1220100120160850302;12201001201608503;68.3642617;59475;17
1220100120160850401;12201001201608504;68.3642617;59475;08
1220100120160850402;12201001201608504;68.3642617;59475;11
1220100120160850403;12201001201608504;68.3642617;59475;13
1220100120160850404;12201001201608504;68.3642617;59475;16

获得

New person  12201001201608502 114.384332 
Old person 12201001201608502 12201001201608502
New person  12201001201608503 68.3642617 
Old person 12201001201608503 12201001201608503
Old person 12201001201608504 12201001201608503
Old person 12201001201608504 12201001201608503
Old person 12201001201608504 12201001201608503
Old person 12201001201608504 12201001201608503

这是错误的,因为它没有检测到 12201001201608504 是新人! 有什么解释吗?

【问题讨论】:

预期输出 欢迎来到 Stack Overflow。请注意,在这里说“谢谢”的首选方式是投票赞成好的问题和有用的答案(一旦你有足够的声誉这样做),并接受对你提出的任何问题最有帮助的答案(这也给出了你的声誉小幅提升)。请查看About 页面以及How do I ask questions here? 和What do I do when someone answers my question? 【参考方案1】:

我认为问题与awk 如何将字符串转换为数字有关,但我没有立即看到中断发生的值的意义。我能够使用 Apple 版本的 Awk 和 GNU Awk(4.1.3 测试)在 Mac 上重现该问题。

可以通过强制进行字符串比较而不是数字比较来解决问题,如下所示:

BEGIN  FS=";"; id=0; 


  if ($2 == id "") .    # Probably important
    print "Old person", $2, id;
   else 
    id = $2 "";          # Possibly important
    COEQ = $3;
    print "New person", id, COEQ
  

在您的数据文件上,会产生:

New person 12201001201608502 114.384332 
Old person 12201001201608502 12201001201608502
New person 12201001201608503 68.3642617 
Old person 12201001201608503 12201001201608503
New person 12201001201608504 68.3642617 
Old person 12201001201608504 12201001201608504
Old person 12201001201608504 12201001201608504
Old person 12201001201608504 12201001201608504

我已经删除了脚本的各种无关部分。我不相信应该使用COEQ,尽管没有造成太大的伤害。

【讨论】:

id=0 不需要初始化并且强制变量为数字。但是,删除不会解决它,但您只需要在分配时进行字符串化。这是awk 中的一个陷阱 谢谢。我没有试验这两个“通过附加一个空字符串转换为字符串”中的哪一个是必要的——两者都有效;任何一个似乎都足够了(至少如果 id = 0 被省略)。不初始化id 也是一个简单的改进(在命令行上使用-F';' 然后消除BEGIN 块)。我认为关键点仍然是“Awk 可以灵活地将字符串视为数字,反之亦然”,以及“比较失败,因为值被转换为数字,当数字有太多数字时这会丢失信息”。 如果你不使用 $2"" == id 并且不在 BEGIN 部​​分初始化 id 那么第一个比较是 strnum-vs-unassigned (数字字符串类型的变体),它是仍然是数字所以虽然它可能会用给定的输入值做你想要的,恕我直言,如果不是更准确的话,强制字符串比较至少更清楚,一旦你这样做了,你就不需要将 $2 转换为字符串id=$2"" 分配。 谢谢乔纳森,对我来说工作得很好。我确实怀疑转换为整数进行比较,但没有找到如何强制字符串比较。再次感谢! 要清楚,问题是@JonathanLeffler 提到的数字转换,但不是 MoezK 在他们的评论中提到的整数转换,而是浮点数的转换。【参考方案2】:

高尔夫版:

$ awk -F\; 'print (f=id==$2)?"Old":"New","person",$2,f?$2:$3;id=$2""' file

New person 12201001201608502 114.384332
Old person 12201001201608502 12201001201608502
New person 12201001201608503 68.3642617
Old person 12201001201608503 12201001201608503
New person 12201001201608504 68.3642617
Old person 12201001201608504 12201001201608504
Old person 12201001201608504 12201001201608504
Old person 12201001201608504 12201001201608504

【讨论】:

【参考方案3】:

在 awk 中,任何看起来像数字的输入都被认为是“strnum”类型(如果您愿意,也可以是数字字符串),因此可以根据上下文在脚本中将其视为数字或字符串。当您将“strnum”与另一个“strnum”(或“数字”)进行比较时,完成的比较类型是数字。在内部,awk 中的所有数字都是浮点数。您正在尝试对一个太大而无法准确表示为系统上的浮点数的数字进行数字比较。试试这个(注意 + 符号前数字末尾的 01):

$ awk 'BEGINprint 12201001201608500+0'
12201001201608500

$ awk 'BEGINprint 12201001201608501+0'
12201001201608500

看到这样的失败。正如其他人指出的那样,解决方案是通过强制 awk 将您的输入视为类型“string”而不是“strnum”来强制进行字符串比较而不是数字比较,您可以通过执行字符串操作来执行此操作,并与 @987654325 连接@,就可以了:

if( ($2"") == id)

我在$2 == id 比较而不是id=$2 分配中这样做,因为第一次比较发生在第一次id=$2 分配之前(否则,给定较早id=0 的第一次比较将是“strnum”与"number" 是一个数字比较),这就是真正关心差异的行。

【讨论】:

以上是关于awk 混淆了两个包含数字的不同字符串的主要内容,如果未能解决你的问题,请参考以下文章

使用 awk 匹配两个不同文件中的数字

shell脚本应用正则表达式grep,sed,awk,的应用

shell脚本应用正则表达式grep,sed,awk,的应用

如何使用AWK将包含特定字符串的行之后的行的第三列中的值打印到不同的文件?

awk 如何判断一个字符串是不是为纯数字?

求教awk两个字符之间截取字符串的方法