在Ruby中动态创建多维哈希表

10

我是一名PHP开发者,正在努力提高自己的Ruby技能。我现在正在尝试开发一个源代码审计工具,用于扫描多种web编程语言的webapp文件中可能存在的危险函数。当匹配项被找到时,脚本会将相关信息保存在一个poi(point-of-interest)类中,以便稍后显示。

该类的一个示例实例如下(采用YAML格式):

poi:
    file_type: "php"
    file: "the-scanned-file.php"
    line_number: 100
    match: "eval()"
    snippet: "echo eval()"

在展示方面,我希望将这些感兴趣的点按照以下方式进行组织:
- file_type
-- file
--- match (the searched payload)

因此,在演示之前,我试图将一个扁平的poi对象数组结构化成一个镜像上述结构的哈希表。这将使我能够简单地遍历哈希表中的项目以产生所需的屏幕组织。(或者至少,这是计划中的内容。)
现在,我的问题是:如何在Ruby中实现这个?
在PHP中,我可以轻松地做到这一点:
<?php

$sorted_pois = array();
foreach($points_of_interest as $point){
    $sorted_pois[$point->file_type][$point->file][$point->match][] = $point;
}

?>

我尝试将这个想法从PHP翻译成Ruby,但是没有成功:

我已经尝试将此思想从PHP翻译为Ruby,但没有成功:

sorted_pois = {}
@points_of_interest.each_with_index do |point, index|
    sorted_pois[point.file_type.to_sym][point.file.to_sym][point.match.to_sym].push point
end

我花了几个小时时间,现在感觉有些力不从心。这段代码与Ruby有关,请问应该如何处理?

更新:

为了参考,这是我定义的精确方法:

# sort the points of interest into a structured hash
def sort
  sorted_pois = {}
  @points_of_interest.each_with_index do |point, index|
    sorted_pois[point.file_type.to_sym][point.file.to_sym][point.match.to_sym].push point
  end
end

运行代码时我收到的错误信息为:

./lib/models/vulnscanner.rb:63:in `sort': undefined method `[]' for nil:NilClass (NoMethodError)
    from /usr/lib/ruby/1.8/rubygems/custom_require.rb:31:in `each_with_index'
    from ./lib/models/vulnscanner.rb:62:in `each'
    from ./lib/models/vulnscanner.rb:62:in `each_with_index'
    from ./lib/models/vulnscanner.rb:62:in `sort'
    from ./webapp-vulnscan:69

第62行(你可能已经猜到了)就是特定的这一行:

@points_of_interest.each_with_index do |point, index|

作为额外的参考,这是将@points_of_interest转换为YAML格式后的样子(以下是代码片段):
- !ruby/object:PoI 
  file: models/couponkimoffer.php
  file_type: php
  group: :dangerous_functions
  line_number: "472"
  match: `
  snippet: ORDER BY `created_at` DESC
- !ruby/object:PoI 
  file: models/couponkimoffer.php
  file_type: php
  group: :dangerous_functions
  line_number: "818"
  match: `
  snippet: WHERE `company_slug` = '$company_slug'
- !ruby/object:PoI 
  file: models/couponkimoffer.php
  file_type: php
  group: :dangerous_functions
  line_number: "819"
  match: `
  snippet: ORDER BY `created_at` DESC

1
你的代码有什么问题吗?它是否导致错误或输出结果与你的期望不符?同时,提供样例输入/输出会更有帮助。 - Andrew Marshall
@AndrewMarshall,感谢您的关注。我刚刚更新了问题。 - Chris Allen Lane
3个回答

33

@John提供的Enumerable#group_by建议是解决您需求的好方法之一。另一种方法是创建一个自动填充的哈希表(就像您在PHP中所拥有的那样),可以这样实现:

hash = Hash.new{ |h,k| h[k] = Hash.new(&h.default_proc) }
hash[:a][:b][:c] = 42
p hash
#=> {:a=>{:b=>{:c=>42}}}
请注意,这种自动创建嵌套结构的方法可能会很“危险”,如果您访问不存在的键,它将为您创建它们。
p hash["does this exist?"]
#=> {}

p hash
#=> {:a=>{:b=>{:c=>42}}, "does this exist?"=>{}}

如果您使用 key? 先对键进行测试,那么即使使用了具有活化作用的 default_proc 也不会遇到这种问题:

val = hash["OH NOES"] if hash.key?("OH NOES")
#=> nil

p hash
#=> {:a=>{:b=>{:c=>42}}, "does this exist?"=>{}}

值得一提的是,你得到的错误信息是这样的:"嘿,你在一个返回了nil的东西后面加了[],而nil没有[]方法。" 具体来说,你的代码...

sorted_pois[point.file_type.to_sym]

计算结果为nil(因为哈希表尚未为此键设置值),然后您尝试请求

nil[point.file.to_sym]

@Phrogz,感谢你抽出时间向我解释。我真的开始喜欢Ruby了,但是它真的很棘手!这让我明显意识到我还需要多读一些书 :) - Chris Allen Lane
非常好的回答!真的帮助我理解了!感谢您能够这么好地解释事情。 - BlackHatSamurai

9
您可能会对group_by感兴趣。
使用示例:
birds = ["Golden Eagle", "Gyrfalcon", "American Robin",
         "Mountain BlueBird", "Mountain-Hawk Eagle"]
grouped_by_first_letter = birds.group_by { |s| s[0] }

# { "G"=>["Golden Eagle", "Gyrfalcon"], "A"=>["American Robin"],
#   "M"=>["Mountain BlueBird", "Mountain-Hawk Eagle"] }

2
如果你能展示如何使用它,而不仅仅是链接到文档,那么正确的回答将获得加1赞。如果能进一步证明其用途,则可能会有更多的赞。 - Phrogz

2
显而易见,上述示例中尝试使用的嵌套哈希和数组不存在。请尝试以下内容:
sorted_pois = {}
pois.each do |point|
  # sanitize data - convert to hash of symbolized keys and values
  poi = Hash[ %w{file_type file match}.map do |key| 
    [key.to_sym, point.send(key).to_sym]
  end ]

  # create nested hash/array if it doesn't already exist
  sorted_pois[ poi[:file_type] ] ||= {}
  sorted_pois[ poi[:file_type] ][ poi[:file] ] ||= {}
  sorted_pois[ poi[:file_type] ][ poi[:file] ][ poi[:match] ] ||= []

  sorted_pois[ poi[:file_type] ][ poi[:file] ][ poi[:match] ] << point
end

这是手动创建嵌套的“更安全”的方法;请参阅我的答案,了解一种不太安全但更方便的方法。 - Phrogz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接