【问题标题】:What's the best way to parse a tab-delimited file in Ruby?在 Ruby 中解析制表符分隔文件的最佳方法是什么?
【发布时间】:2011-05-23 05:33:42
【问题描述】:

在 Ruby 中解析制表符分隔文件的最佳(最有效)方法是什么?

【问题讨论】:

    标签: ruby tsv


    【解决方案1】:

    实际上有两种不同类型的 TSV 文件。

    1. TSV 文件实际上是分隔符设置为 Tab 的 CSV 文件。这是你会得到的东西,例如将 Excel 电子表格另存为“UTF-16 Unicode 文本”。此类文件使用 CSV 引用规则,这意味着字段可能包含制表符和换行符,只要它们被引用,并且文字双引号被写入两次。正确解析所有内容的最简单方法是使用 csv gem:

      use 'csv'
      parsed = CSV.read("file.tsv", col_sep: "\t")
      
    2. 符合IANA standard 的TSV 文件。不允许使用制表符和换行符作为字段值,并且没有任何引用。这是你会得到的东西,例如选择整个 Excel 电子表格并将其粘贴到文本文件中(注意:如果某些单元格确实包含制表符或换行符,它将变得混乱)。可以使用简单的line.rstrip.split("\t", -1) 轻松逐行解析此类 TSV 文件(注意 -1,它可以防止 split 删除空的尾随字段)。如果您想使用csv gem,只需将quote_char 设置为nil

      use 'csv'
      parsed = CSV.read("file.tsv", col_sep: "\t", quote_char: nil)
      

    【讨论】:

    • 在实践中,我发现 quote_char: nil 在 Ruby 2.7.0 中为 nil:NilClass (NoMethodError)` 获得了 undefined method encode'。另一个 SO 线程建议使用 "\0"liberal_parsing: true,这对我来说效果更好,但两者都可能因 IANA TSV 包含转义字符而失败:stackoverflow.com/a/41644206/2960236
    【解决方案2】:

    我喜欢 mmmries 的回答。但是,我讨厌 ruby​​ 从拆分结束时剥离任何空值的方式。它也不会在行尾剥离换行符。

    另外,我有一个字段中包含潜在换行符的文件。所以,我重写了他的“解析”如下:

    def parse
      open(filepath) do |f|
        headers = f.gets.strip.split("\t")
        f.each do |line|
          myline=line
          while myline.scan(/\t/).count != headers.count-1
            myline+=f.gets
          end
          fields = Hash[headers.zip(myline.chomp.split("\t",headers.count))]
          yield fields
        end
      end
    end
    

    这会根据需要连接任何行以获得完整的数据行,并始终返回完整的数据集(最后没有潜在的 nil 条目)。

    【讨论】:

      【解决方案3】:

      TSV 的规则实际上与 CSV 有点不同。主要区别在于 CSV 提供了在字段内粘贴逗号,然后在字段内使用引号字符和转义引号的规定。我写了一个简单的例子来展示简单的响应是如何失败的:

      require 'csv'
      line = 'boogie\ttime\tis "now"'
      begin
        line = CSV.parse_line(line, col_sep: "\t")
        puts "parsed correctly"
      rescue CSV::MalformedCSVError
        puts "failed to parse line"
      end
      
      begin
        line = CSV.parse_line(line, col_sep: "\t", quote_char: "Ƃ")
        puts "parsed correctly with random quote char"
      rescue CSV::MalformedCSVError
        puts "failed to parse line with random quote char"
      end
      
      #Output:
      # failed to parse line
      # parsed correctly with random quote char
      

      如果您想使用 CSV 库,您可以使用您不希望在文件中看到的随机引号字符(示例显示了这一点),但您也可以使用更简单的方法,例如下面显示的 StrictTsv 类无需担心字段引用即可获得相同的效果。

      # The main parse method is mostly borrowed from a tweet by @JEG2
      class StrictTsv
        attr_reader :filepath
        def initialize(filepath)
          @filepath = filepath
        end
      
        def parse
          open(filepath) do |f|
            headers = f.gets.strip.split("\t")
            f.each do |line|
              fields = Hash[headers.zip(line.split("\t"))]
              yield fields
            end
          end
        end
      end
      
      # Example Usage
      tsv = Vendor::StrictTsv.new("your_file.tsv")
      tsv.parse do |row|
        puts row['named field']
      end
      

      选择使用 CSV 库还是更严格的库仅取决于向您发送文件的人以及他们是否希望遵守严格的 TSV 标准。

      有关 TSV 标准的详细信息,请访问http://en.wikipedia.org/wiki/Tab-separated_values

      【讨论】:

      • 请在答案中包含代码 sn-ps,not 在外部要点中。现在看来,这个要点似乎已经失效,这真是一种耻辱。
      • @JezenThomas 感谢您的提醒。我内联提取了所有代码示例以解决必须查看要点的问题
      • 很好的答案。 ? .我很惊讶\d 使用 CSV 解析器失败了。
      • 这是次要的,但会让我误入歧途。 line = 'boogie\ttime\tis "now"' 产生一个带有双转义制表符的字符串,所以我认为失败可能是由于我实际上只是错误地编写了我的测试。要获得预期的测试字符串,请使用line = "boogie\ttime\tis \"now\"""boogie\ttime\tis " + '"now"' 您可以使用puts 对其进行测试。第一个结果为boogie\ttime\tis "now",而后两个结果为boogie time is "now"(选项卡在此处显示不佳,但会在您的控制台中显示)。感谢您的全面回答?
      • 我刚刚发现,至少在 Ruby 2.5.0 中,这与 TSV 特别相关,但由于引号的放置,整个 CSV 库和规范。以下两个都将失败CSV.parse("foo,bar,and \"baz\" quotes")CSV.parse("foo\tbar\tand \"baz\" quotes", col_sep: "\t")。看来引号仅在它们包围整个列的内容时才有效,以便您可以包含列分隔符。以下两个解析好CSV.parse("foo\tbar\t\"and baz\tquotes\"", col_sep: "\t")CSV.parse("foo,bar,\"and baz,quotes\"")
      【解决方案4】:

      Ruby CSV 库允许您指定字段分隔符。 Ruby 1.9 使用FasterCSV。这样的事情会起作用:

      require "csv"
      parsed_file = CSV.read("path-to-file.csv", { :col_sep => "\t" })
      

      【讨论】:

      • 请注意,如果任何制表符分隔的值包含双引号,此方法将失败。另一个答案中的 StrictTsv 建议更可靠。
      • 如果您的文件中不正确地使用了双引号,您可以将" 替换为您不希望出现在文件中的其他字符。 parsed_file = CSV.read("path-to-file.csv", { col_sep: "\t", quote_char: '}') 来源docs.
      • 另一种方法是使用liberal_parsing 选项。 parsed_file = CSV.read("path-to-file.csv", { col_sep: "\t", liberal_parsing: true) 来源docs.
      • 如果您想读取文件中出现的双引号,只需将quote_char 设置为nilparsed_file = CSV.read("path-to-file.csv", col_sep: "\t", quote_char: nil)。这比使用您认为不会出现在文件中的字符更加健壮和优雅。
      • CSV 库使用的最新版本不希望您使用周围的花括号,如果您使用“警告:不推荐使用最后一个参数作为关键字参数;也许 **应添加到通话中”
      猜你喜欢
      • 2017-02-03
      • 2010-09-14
      • 2010-09-30
      • 2010-11-16
      • 1970-01-01
      • 2010-12-15
      • 1970-01-01
      • 2010-09-08
      • 1970-01-01
      相关资源
      最近更新 更多