当单元格/字段包含前导双引号时,Ruby不会解析CSV

时间:2016-02-07 15:59:43

标签: ruby csv

如果其中一列包含双引号“字符,我将如何解析CSV文件? 我得到了“行xxx中的丢失或流浪报价”错误,因为其中有一个尾随的双引号。确切的错误是“第58行(CSV :: MalformedCSVError)中的丢失或流浪引用”。数据来自解析另一个设备(防火墙)配置的应用程序,并且“已被管理员添加为对此设备配置的注释,因此无法控制。

示例输入数据(无法提供文件,它们本质上是敏感的):

"Table 1 Firewall Policy from INT to EXT administrative service rules on TestFirewall","1","Yes","Allow","[Group] GreenServer","[Host] Any","[Group] FTP","No",""Access"^M

正如您所看到的,最后一栏中的评论是“”Access“。如果最后一栏中只有一个双引号,我到目前为止的脚本似乎运行得很好。

复制所需的最少代码:

#!/usr/bin/env ruby
require 'csv'
require 'pp'
nipperfiles = Dir.glob(ARGV[0] + '/*.csv')

def allcsv(nipperfiles)
  filearray = []
  nipperfiles.each do |csv|
  filearray << csv
  end

  filearray
end

def devicetype(filelist)
  filelist.each do |f|
  CSV.foreach(f, :headers => true, :force_quotes => true, :encoding => Encoding::UTF_8) do |row|
    if row["Table"] =~ /audit device list/ && row["OS"] =~ /FortiOS/
      return "Fortigate"
    end
    end
  end
end

filelist = allcsv(nipperfiles)
device = devicetype(filelist)

理想情况下,工作代码会忽略额外的引用或替换它或任何其他可能有问题的字符。值得注意的是,鉴于原始防火墙配置是由一个人配置的,该人可以将额外的报价放在任何一个单元/字段中。

5 个答案:

答案 0 :(得分:3)

这是一个可能有用的技巧。使用:quote_char => "'"(假设CSV中的列中的值没有单引号字符),这将在读取值中包含双引号 - 您可以通过代码删除它:

示例:

CSV.foreach(f, :force_quotes => true, :encoding => Encoding::UTF_8,
               :quote_char => "'") do |row|
   puts row[0]
   #=> "Table 1 Firewall ... administrative service rules on TestFirewall"
   puts row[0][1..-2]
   #=> Table 1 Firewall ... administrative service rules on TestFirewall
end

仅供参考:您可以将最不可能出现在CSV文本中的任何字符用作:quote_char,以上解决方案仍可使用

如果上述方法不起作用,那么最好将每一行作为字符串处理并在其上使用split而不是使用CSV类。

File.open("/path/to/file") do |f|
  f.each_line do |for|
    columns = row.split(",")
  end
end

答案 1 :(得分:1)

您可以从CSV::MalformedCSVError进行救援,并为出现此类问题的行创建单独的处理程序,但这意味着您必须单独解析每一行,并且从标题行中丢失列名。

require 'csv'

File.open('csv.csv').each_line do |input_row|
  begin
    CSV.parse(input_row) do |row|
      puts row.inspect
    end
  rescue CSV::MalformedCSVError => error
    if input_row.include?('""')
      input_row.gsub!('""', '"')
      retry
    else
      raise error
    end
  end
end

我有点惊讶没有像:on_malformed_csv => lambda ...这样的选项。

答案 2 :(得分:1)

Tin Man的想法证明是最好的,基本上读取所有文件,改变我不想要的位。该脚本随后会写入可由CSV类读取的已清理文件。如果需要,这允许我在线下添加更多替换。

我选择使用the Rio gem来完成工作。

代码的基本思想:

cleanme = Dir.glob(ARGV[0])
def cleanfiles(cleanme)
  puts "Cleaning up CSV files"
  rio(cleanme).all.files('*.csv') do |f|
    puts "Reading and Cleaning File: #{f}"
    rio(f) <f.contents.gsub("''", "Empty").gsub(/""\w+"/, '"Comment Malformed and Removed"').gsub("\r\n", "\r")
  end
end

如果有错误的评论引发了错误,我会使用&#34;评论格式错误和删除&#34;线。这允许我团队中的人员将引用交叉引用回原始输入文件,并找出评论应该是什么。任何空字段(技术上&#34;&#39;&#34;&#34;)都会被字符串&#34;空&#34;替换。

答案 3 :(得分:0)

,"No",""Access"

这是格式错误的csv,因为内部(双)引用应该使用另一个引用(或某些系统上的\)进行转义:

,"No","""Access"

您可以尝试在单行行中修复此问题,但是:

  

该人可以在几乎所有单元格/字段中添加额外的引用。

     

单元格内也有换行符/换行符。 &#34;未加引号的字段   不允许\ r或\ n&#34;。不幸的是,字段内的换行符   是必需的。

嗯,现在你真的遇到了麻烦。你甚至不应该试图解决这个问题,但要联系创作者并让他修正他的输出。

虽然您可能能够针对当前数据修复它,但这将在未来继续造成麻烦。

答案 4 :(得分:0)

最简单的解决方案是打开文件,逐行阅读并忽略第一行

File.readlines("test.csv").drop(1).each do |line|
    CSV.parse(line) do |row|
        puts row.inspect
    end
end