在 Ruby 中解析制表符分隔文件的最佳(最有效)方法是什么?
4 回答
Ruby CSV 库允许您指定字段分隔符。Ruby 1.9 使用FasterCSV。像这样的东西会起作用:
require "csv"
parsed_file = CSV.read("path-to-file.csv", col_sep: "\t")
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
实际上有两种不同类型的 TSV 文件。
TSV 文件实际上是分隔符设置为 Tab 的 CSV 文件。例如,当您将 Excel 电子表格另存为“UTF-16 Unicode 文本”时,您会遇到这种情况。此类文件使用 CSV 引用规则,这意味着字段可能包含制表符和换行符,只要它们被引用,并且文字双引号被写入两次。正确解析所有内容的最简单方法是使用
csv
gem:use 'csv' parsed = CSV.read("file.tsv", col_sep: "\t")
符合IANA 标准的 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)
我喜欢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 条目)。