如何使用Ruby从CSV中删除一行

7
给定以下CSV文件,如何删除包含列“foo”中单词'true'的所有行?
Date,foo,bar
2014/10/31,true,derp
2014/10/31,false,derp

我已经有一个可行的解决方案,不过需要创建一个名为csv_no_foo的二次CSV对象。

@csv = CSV.read(@csvfile, headers: true) #http://bit.ly/1mSlqfA
@headers = CSV.open(@csvfile,'r', :headers => true).read.headers

# Make a new CSV
@csv_no_foo = CSV.new(@headers)

@csv.each do |row|
  # puts row[5]
  if row[@headersHash['foo']] == 'false'
    @csv_no_foo.add_row(row)
  else
    puts "not pushing row #{row}"
  end
end

理想情况下,我只需从CSV中删除有问题的行,就像这样:
...
 if row[@headersHash['foo']] == 'false'
    @csv.delete(true) #Doesn't work
...

看了一下ruby文档,好像row类有一个delete_if函数。我对这个函数的语法感到困惑。有没有一种方法可以在不创建新csv对象的情况下删除行? http://ruby-doc.org/stdlib-1.9.2/libdoc/csv/rdoc/CSV/Row.html#method-i-each

你确定一定要使用 Ruby 吗?我在想 awk 也许也可以。 - Jared Beck
我收回之前的说法 :) awk 不是一个好选择,因为, 可能是分隔符或带引号值的一部分。 - Jared Beck
重写CSV并删除有问题的行是解决方案。您正在尝试从具有可变长度记录的文件中删除一系列字节,通常的方法是复制文件并在此过程中进行过滤。 - mu is too short
谢谢,虽然我不确定我理解了。你是指如何重写CSV文件吗?你是指重写到磁盘上吗?在写入磁盘之前,我还有更多的操作要做,并且我想避免两次读取CSV文件。 - spuder
2个回答

17

您应该能够使用CSV::Table#delete_if,但是您需要使用CSV::table而不是CSV::read,因为前者会给您提供一个CSV::Table对象,而后者会产生一个数组。请注意,这个设置还会将标题转换为符号。

table = CSV.table(@csvfile)

table.delete_if do |row|
  row[:foo] == 'true'
end

File.open(@csvfile, 'w') do |f|
  f.write(table.to_csv)
end

我正在使用这种方法,如果删除的行是表中的最后一行,则整个CSV内容都将被删除 - 换句话说,在所有行被删除后,标题也将被删除。有人知道如何防止标题被删除吗? - sealocal
@sealocal 或许在调用 to_csv 时添加选项 write_headers: true 会有所帮助(现在无法测试)。 - Patrick Oscity
嗯,我尝试过了,但没有成功。虽然我不确定是不是用错了。我决定检查一下新文件是否少于2行,然后再将文件重新写成一个CSV文件,只有一行 - 标题行。 - sealocal

1

你可能想以Ruby的方式过滤行:

require 'csv' 
csv = CSV.parse(File.read(@csvfile), {
  :col_sep => ",", 
  :headers => true
  }
).collect { |item| item[:foo] != 'true' }

希望它有所帮助。

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