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

问题描述 投票:1回答:5

当其中一列包含双引号“字符时,我将如何解析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)

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

ruby csv
5个回答
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及以上解决方案仍然有效


如果上面不起作用,那么,最好将每一行作为String处理并在其上使用split而不是使用CSV类。

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

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 ...这样的选项。


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

只要有错误的评论引发错误,我就会用“评论格式错误和删除”行替换为流浪引号。这允许我团队中的人员将引用交叉引用回原始输入文件,并找出评论应该是什么。任何空字段(技术上为“''”)都将替换为字符串“Empty”。


0
投票
,"No",""Access"

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

,"No","""Access"

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

那个人可以在几乎任何细胞/田地中加上额外的报价。

单元格内部还有换行符/换行符。 “未加引号的字段不允许\ r或\ n”。不幸的是,字段内的换行符是必需的。

好吧,现在你真的遇到了麻烦。您甚至不应该尝试解决此问题,但请联系创建者并让他修复输出。

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


0
投票

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

File.readlines("test.csv").drop(1).each do |line|
    CSV.parse(line) do |row|
        puts row.inspect
    end
end
© www.soinside.com 2019 - 2024. All rights reserved.