类 CSV

CSV

CSV Data

CSV(逗号分隔值)数据是表格的文本表示

此 CSV 字符串的行分隔符为"\n",列分隔符为",",有 3 行和 2 列

"foo,0\nbar,1\nbaz,2\n"

尽管名称为 CSV,但 CSV 表示可以使用不同的分隔符。

有关表格的更多信息,请参阅维基百科文章“表(信息)”,特别是其“简单表”部分

类 CSV

CSV 提供用于以下操作的方法

使 CSV 可用

require 'csv'

此处的所有示例均假设已完成此操作。

保持简单

CSV 对象有几十个实例方法,可对解析和生成 CSV 数据进行细粒度控制。但是,对于许多需求,更简单的方法就足够了。

本节总结了 CSV 中允许您在不显式创建 CSV 对象的情况下进行解析和生成的单例方法。有关详细信息,请单击链接。

简单解析

解析方法通常返回以下两种方法之一

解析字符串

要解析的输入可以是字符串

string = "foo,0\nbar,1\nbaz,2\n"

方法 CSV.parse 返回整个 CSV 数据

CSV.parse(string) # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]

方法 CSV.parse_line 仅返回第一行

CSV.parse_line(string) # => ["foo", "0"]

CSV 使用实例方法 String#parse_csv 扩展类 String,该方法也仅返回第一行

string.parse_csv # => ["foo", "0"]

通过文件路径进行解析

要解析的输入可以位于文件中

string = "foo,0\nbar,1\nbaz,2\n"
path = 't.csv'
File.write(path, string)

方法 CSV.read 返回整个 CSV 数据

CSV.read(path) # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]

方法 CSV.foreach 进行迭代,将每一行传递给给定的块

CSV.foreach(path) do |row|
  p row
end

输出

["foo", "0"]
["bar", "1"]
["baz", "2"]

方法 CSV.table 将整个 CSV 数据作为 CSV::Table 对象返回

CSV.table(path) # => #<CSV::Table mode:col_or_row row_count:3>

从打开的 IO 流中进行解析

要解析的输入可以位于打开的 IO 流中

方法 CSV.read 返回整个 CSV 数据

File.open(path) do |file|
  CSV.read(file)
end # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]

方法 CSV.parse 也是如此

File.open(path) do |file|
  CSV.parse(file)
end # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]

方法 CSV.parse_line 仅返回第一行

File.open(path) do |file|
 CSV.parse_line(file)
end # => ["foo", "0"]

方法 CSV.foreach 进行迭代,将每一行传递给给定的块

File.open(path) do |file|
  CSV.foreach(file) do |row|
    p row
  end
end

输出

["foo", "0"]
["bar", "1"]
["baz", "2"]

方法 CSV.table 将整个 CSV 数据作为 CSV::Table 对象返回

File.open(path) do |file|
  CSV.table(file)
end # => #<CSV::Table mode:col_or_row row_count:3>

简单生成

方法 CSV.generate 返回一个字符串;此示例使用 CSV#<< 方法来追加要生成的那些行

output_string = CSV.generate do |csv|
  csv << ['foo', 0]
  csv << ['bar', 1]
  csv << ['baz', 2]
end
output_string # => "foo,0\nbar,1\nbaz,2\n"

方法 CSV.generate_line 返回一个字符串,其中包含由数组构造的单行

CSV.generate_line(['foo', '0']) # => "foo,0\n"

CSV 使用实例方法 Array#to_csv 扩展了类 Array,该方法将数组转换为字符串

['foo', '0'].to_csv # => "foo,0\n"

“筛选”CSV

方法 CSV.filter 为 CSV 数据提供 Unix 风格的筛选器。处理输入数据以形成输出数据

in_string = "foo,0\nbar,1\nbaz,2\n"
out_string = ''
CSV.filter(in_string, out_string) do |row|
  row[0] = row[0].upcase
  row[1] *= 4
end
out_string # => "FOO,0000\nBAR,1111\nBAZ,2222\n"

CSV 对象

有三种方法可以创建 CSV 对象

实例方法

CSV 有三组实例方法

委托方法

为了方便起见,CSV 对象将委托给类 IO 中的许多方法。(一些方法在 CSV 中有包装“保护代码”。)您可以调用

选项

选项的默认值为

DEFAULT_OPTIONS = {
  # For both parsing and generating.
  col_sep:            ",",
  row_sep:            :auto,
  quote_char:         '"',
  # For parsing.
  field_size_limit:   nil,
  converters:         nil,
  unconverted_fields: nil,
  headers:            false,
  return_headers:     false,
  header_converters:  nil,
  skip_blanks:        false,
  skip_lines:         nil,
  liberal_parsing:    false,
  nil_value:          nil,
  empty_value:        "",
  strip:              false,
  # For generating.
  write_headers:      nil,
  quote_empty:        true,
  force_quotes:       false,
  write_converters:   nil,
  write_nil_value:    nil,
  write_empty_value:  "",
}

解析选项

下面详细描述了解析选项,包括

选项 row_sep

指定行分隔符(字符串或符号 :auto(见下文))用于解析和生成。

默认值

CSV::DEFAULT_OPTIONS.fetch(:row_sep) # => :auto

row_sep 为字符串时,该字符串将成为行分隔符。String 在使用前将被转码为数据的 Encoding

使用 "\n"

row_sep = "\n"
str = CSV.generate(row_sep: row_sep) do |csv|
  csv << [:foo, 0]
  csv << [:bar, 1]
  csv << [:baz, 2]
end
str # => "foo,0\nbar,1\nbaz,2\n"
ary = CSV.parse(str)
ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]

使用 |(管道)

row_sep = '|'
str = CSV.generate(row_sep: row_sep) do |csv|
  csv << [:foo, 0]
  csv << [:bar, 1]
  csv << [:baz, 2]
end
str # => "foo,0|bar,1|baz,2|"
ary = CSV.parse(str, row_sep: row_sep)
ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]

使用 --(两个连字符)

row_sep = '--'
str = CSV.generate(row_sep: row_sep) do |csv|
  csv << [:foo, 0]
  csv << [:bar, 1]
  csv << [:baz, 2]
end
str # => "foo,0--bar,1--baz,2--"
ary = CSV.parse(str, row_sep: row_sep)
ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]

使用 ''(空字符串)

row_sep = ''
str = CSV.generate(row_sep: row_sep) do |csv|
  csv << [:foo, 0]
  csv << [:bar, 1]
  csv << [:baz, 2]
end
str # => "foo,0bar,1baz,2"
ary = CSV.parse(str, row_sep: row_sep)
ary # => [["foo", "0bar", "1baz", "2"]]

row_sep 是符号 :auto(默认值)时,生成会使用 "\n" 作为行分隔符

str = CSV.generate do |csv|
  csv << [:foo, 0]
  csv << [:bar, 1]
  csv << [:baz, 2]
end
str # => "foo,0\nbar,1\nbaz,2\n"

另一方面,解析会调用行分隔符的自动发现。

自动发现会提前读取数据,寻找下一个 \r\n\n\r 序列。即使该序列出现在带引号的字段中,也会选择该序列,假设您会在那里有相同的行尾。

示例

str = CSV.generate do |csv|
  csv << [:foo, 0]
  csv << [:bar, 1]
  csv << [:baz, 2]
end
str # => "foo,0\nbar,1\nbaz,2\n"
ary = CSV.parse(str)
ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]

如果以下任一条件为真,则使用默认的 $INPUT_RECORD_SEPARATOR$/

显然,发现需要一点时间。如果速度很重要,请手动 Set。另请注意,如果要使用此功能,应在 Windows 上以二进制模式打开 IO 对象,因为行尾转换可能会导致将文档位置重置为读取前的位置时出现问题。

选项 col_sep

指定用于解析和生成的字符串字段分隔符。该字符串在使用前将被转码为数据的编码。

默认值

CSV::DEFAULT_OPTIONS.fetch(:col_sep) # => "," (comma)

使用默认值(逗号)

str = CSV.generate do |csv|
  csv << [:foo, 0]
  csv << [:bar, 1]
  csv << [:baz, 2]
end
str # => "foo,0\nbar,1\nbaz,2\n"
ary = CSV.parse(str)
ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]

使用 :(冒号)

col_sep = ':'
str = CSV.generate(col_sep: col_sep) do |csv|
  csv << [:foo, 0]
  csv << [:bar, 1]
  csv << [:baz, 2]
end
str # => "foo:0\nbar:1\nbaz:2\n"
ary = CSV.parse(str, col_sep: col_sep)
ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]

使用 ::(两个冒号)

col_sep = '::'
str = CSV.generate(col_sep: col_sep) do |csv|
  csv << [:foo, 0]
  csv << [:bar, 1]
  csv << [:baz, 2]
end
str # => "foo::0\nbar::1\nbaz::2\n"
ary = CSV.parse(str, col_sep: col_sep)
ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]

使用 ''(空字符串)

col_sep = ''
str = CSV.generate(col_sep: col_sep) do |csv|
  csv << [:foo, 0]
  csv << [:bar, 1]
  csv << [:baz, 2]
end
str # => "foo0\nbar1\nbaz2\n"

使用空字符串解析时引发异常

col_sep = ''
# Raises ArgumentError (:col_sep must be 1 or more characters: "")
CSV.parse("foo0\nbar1\nbaz2\n", col_sep: col_sep)
选项 quote_char

指定用于在解析和生成中引用字段的字符(长度为 1 的字符串)。此 String 在使用前将被转码为数据的编码。

默认值

CSV::DEFAULT_OPTIONS.fetch(:quote_char) # => "\"" (double quote)

这对于错误地使用 '(单引号)而不是正确的 "(双引号)引用字段的应用程序很有用。

使用默认值(双引号)

str = CSV.generate do |csv|
  csv << ['foo', 0]
  csv << ["'bar'", 1]
  csv << ['"baz"', 2]
end
str # => "foo,0\n'bar',1\n\"\"\"baz\"\"\",2\n"
ary = CSV.parse(str)
ary # => [["foo", "0"], ["'bar'", "1"], ["\"baz\"", "2"]]

使用 '(单引号)

quote_char = "'"
str = CSV.generate(quote_char: quote_char) do |csv|
  csv << ['foo', 0]
  csv << ["'bar'", 1]
  csv << ['"baz"', 2]
end
str # => "foo,0\n'''bar''',1\n\"baz\",2\n"
ary = CSV.parse(str, quote_char: quote_char)
ary # => [["foo", "0"], ["'bar'", "1"], ["\"baz\"", "2"]]

如果字符串长度大于 1,则引发异常

# Raises ArgumentError (:quote_char has to be nil or a single character String)
CSV.new('', quote_char: 'xx')

如果值不是字符串,则引发异常

# Raises ArgumentError (:quote_char has to be nil or a single character String)
CSV.new('', quote_char: :foo)
选项 field_size_limit

指定整数字段大小限制。

默认值

CSV::DEFAULT_OPTIONS.fetch(:field_size_limit) # => nil

这是 CSV 将提前读取以查找字段的结束引号的最大大小。(实际上,它读取到超出此大小的第一个行尾。)如果在限制内找不到引号,CSV 将引发 MalformedCSVError,假设数据有缺陷。您可以使用此限制来防止对解析器的有效 DoS 攻击。但是,此限制可能会导致合法的解析失败;因此,默认值为 nil(无限制)。

本节中的示例

str = <<~EOT
  "a","b"
  "
  2345
  ",""
EOT
str # => "\"a\",\"b\"\n\"\n2345\n\",\"\"\n"

使用默认的nil

ary = CSV.parse(str)
ary # => [["a", "b"], ["\n2345\n", ""]]

使用50

field_size_limit = 50
ary = CSV.parse(str, field_size_limit: field_size_limit)
ary # => [["a", "b"], ["\n2345\n", ""]]

如果字段太长,则引发异常

big_str = "123456789\n" * 1024
# Raises CSV::MalformedCSVError (Field size exceeded in line 1.)
CSV.parse('valid,fields,"' + big_str + '"', field_size_limit: 2048)
选项converters

指定用于解析字段的转换器。请参见字段转换器

默认值

CSV::DEFAULT_OPTIONS.fetch(:converters) # => nil

该值可以是字段转换器名称(请参见存储的转换器

str = '1,2,3'
# Without a converter
array = CSV.parse_line(str)
array # => ["1", "2", "3"]
# With built-in converter :integer
array = CSV.parse_line(str, converters: :integer)
array # => [1, 2, 3]

该值可以是转换器列表(请参见转换器列表

str = '1,3.14159'
# Without converters
array = CSV.parse_line(str)
array # => ["1", "3.14159"]
# With built-in converters
array = CSV.parse_line(str, converters: [:integer, :float])
array # => [1, 3.14159]

该值可以是 Proc 自定义转换器:(请参见自定义字段转换器

str = ' foo  ,  bar  ,  baz  '
# Without a converter
array = CSV.parse_line(str)
array # => [" foo  ", "  bar  ", "  baz  "]
# With a custom converter
array = CSV.parse_line(str, converters: proc {|field| field.strip })
array # => ["foo", "bar", "baz"]

另请参见自定义字段转换器


如果转换器不是转换器名称或 Proc,则引发异常

str = 'foo,0'
# Raises NoMethodError (undefined method `arity' for nil:NilClass)
CSV.parse(str, converters: :foo)
选项unconverted_fields

指定确定是否可用未转换字段值的布尔值。

默认值

CSV::DEFAULT_OPTIONS.fetch(:unconverted_fields) # => nil

未转换的字段值是源数据中发现的值,在通过选项converters执行任何转换之前。

当选项unconverted_fieldstrue时,每个返回的行(Array 或 CSV::Row)都有一个添加的方法unconverted_fields,它返回未转换的字段值

str = <<-EOT
foo,0
bar,1
baz,2
EOT
# Without unconverted_fields
csv = CSV.parse(str, converters: :integer)
csv # => [["foo", 0], ["bar", 1], ["baz", 2]]
csv.first.respond_to?(:unconverted_fields) # => false
# With unconverted_fields
csv = CSV.parse(str, converters: :integer, unconverted_fields: true)
csv # => [["foo", 0], ["bar", 1], ["baz", 2]]
csv.first.respond_to?(:unconverted_fields) # => true
csv.first.unconverted_fields # => ["foo", "0"]
选项headers

指定要用于定义列标题的布尔值、符号、数组或字符串。

默认值

CSV::DEFAULT_OPTIONS.fetch(:headers) # => false

没有headers

str = <<-EOT
Name,Count
foo,0
bar,1
bax,2
EOT
csv = CSV.new(str)
csv # => #<CSV io_type:StringIO encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
csv.headers # => nil
csv.shift # => ["Name", "Count"]

如果设置为true或符号:first_row,则第一行数据将被视为一行标题

str = <<-EOT
Name,Count
foo,0
bar,1
bax,2
EOT
csv = CSV.new(str, headers: true)
csv # => #<CSV io_type:StringIO encoding:UTF-8 lineno:2 col_sep:"," row_sep:"\n" quote_char:"\"" headers:["Name", "Count"]>
csv.headers # => ["Name", "Count"]
csv.shift # => #<CSV::Row "Name":"bar" "Count":"1">

如果设置为数组,则数组元素将被视为标题

str = <<-EOT
foo,0
bar,1
bax,2
EOT
csv = CSV.new(str, headers: ['Name', 'Count'])
csv
csv.headers # => ["Name", "Count"]
csv.shift # => #<CSV::Row "Name":"bar" "Count":"1">

如果设置为字符串str,则使用当前options调用方法CSV::parse_line(str, options),并将返回的数组视为标题

str = <<-EOT
foo,0
bar,1
bax,2
EOT
csv = CSV.new(str, headers: 'Name,Count')
csv
csv.headers # => ["Name", "Count"]
csv.shift # => #<CSV::Row "Name":"bar" "Count":"1">
选项return_headers

指定确定方法shift返回还是忽略标题行的布尔值。

默认值

CSV::DEFAULT_OPTIONS.fetch(:return_headers) # => false

示例

str = <<-EOT
Name,Count
foo,0
bar,1
bax,2
EOT
# Without return_headers first row is str.
csv = CSV.new(str, headers: true)
csv.shift # => #<CSV::Row "Name":"foo" "Count":"0">
# With return_headers first row is headers.
csv = CSV.new(str, headers: true, return_headers: true)
csv.shift # => #<CSV::Row "Name":"Name" "Count":"Count">
选项header_converters

指定用于解析标题的转换器。请参见标题转换器

默认值

CSV::DEFAULT_OPTIONS.fetch(:header_converters) # => nil

与选项converters的功能相同,但

本部分假设已执行以下操作

str = <<-EOT
Name,Value
foo,0
bar,1
baz,2
EOT
# With no header converter
table = CSV.parse(str, headers: true)
table.headers # => ["Name", "Value"]

该值可能是一个标题转换器名称(请参阅存储的转换器

table = CSV.parse(str, headers: true, header_converters: :downcase)
table.headers # => ["name", "value"]

该值可以是转换器列表(请参见转换器列表

header_converters = [:downcase, :symbol]
table = CSV.parse(str, headers: true, header_converters: header_converters)
table.headers # => [:name, :value]

该值可能是一个 Proc 自定义转换器(请参阅自定义标题转换器

upcase_converter = proc {|field| field.upcase }
table = CSV.parse(str, headers: true, header_converters: upcase_converter)
table.headers # => ["NAME", "VALUE"]

另请参阅自定义标题转换器

选项 skip_blanks

指定一个布尔值,用于确定是否忽略输入中的空行;包含列分隔符的行不被视为空白。

默认值

CSV::DEFAULT_OPTIONS.fetch(:skip_blanks) # => false

另请参阅选项skiplines

有关本部分中的示例

str = <<-EOT
foo,0

bar,1
baz,2

,
EOT

使用默认值 false

ary = CSV.parse(str)
ary # => [["foo", "0"], [], ["bar", "1"], ["baz", "2"], [], [nil, nil]]

使用 true

ary = CSV.parse(str, skip_blanks: true)
ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"], [nil, nil]]

使用真值

ary = CSV.parse(str, skip_blanks: :foo)
ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"], [nil, nil]]
选项 skip_lines

指定一个对象,用于识别输入中要忽略的注释行

默认值

CSV::DEFAULT_OPTIONS.fetch(:skip_lines) # => nil

有关本部分中的示例

str = <<-EOT
# Comment
foo,0
bar,1
baz,2
# Another comment
EOT
str # => "# Comment\nfoo,0\nbar,1\nbaz,2\n# Another comment\n"

使用默认值 nil

ary = CSV.parse(str)
ary # => [["# Comment"], ["foo", "0"], ["bar", "1"], ["baz", "2"], ["# Another comment"]]

使用正则表达式

ary = CSV.parse(str, skip_lines: /^#/)
ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]

使用字符串

ary = CSV.parse(str, skip_lines: '#')
ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]

如果给定的对象不是正则表达式、字符串或 nil,则引发异常

# Raises ArgumentError (:skip_lines has to respond to #match: 0)
CSV.parse(str, skip_lines: 0)
选项 strip

指定布尔值,用于确定是否从每个输入字段中删除空白。

默认值

CSV::DEFAULT_OPTIONS.fetch(:strip) # => false

默认值为 false

ary = CSV.parse_line(' a , b ')
ary # => [" a ", " b "]

值为 true

ary = CSV.parse_line(' a , b ', strip: true)
ary # => ["a", "b"]
选项 liberal_parsing

指定布尔值或哈希值,用于确定CSV是否尝试解析不符合 RFC 4180 的输入,例如未加引号的字段中的双引号。

默认值

CSV::DEFAULT_OPTIONS.fetch(:liberal_parsing) # => false

对于接下来的两个示例

str = 'is,this "three, or four",fields'

不使用 liberal_parsing

# Raises CSV::MalformedCSVError (Illegal quoting in str 1.)
CSV.parse_line(str)

使用 liberal_parsing

ary = CSV.parse_line(str, liberal_parsing: true)
ary # => ["is", "this \"three", " or four\"", "fields"]

使用 backslash_quote 子选项解析使用反斜杠转义双引号字符的值。这会导致解析器将 \" 视为 ""

对于接下来的两个示例

str = 'Show,"Harry \"Handcuff\" Houdini, the one and only","Tampa Theater"'

使用 liberal_parsing,但不使用 backslash_quote 子选项

# Incorrect interpretation of backslash; incorrectly interprets the quoted comma as a field separator.
ary = CSV.parse_line(str, liberal_parsing: true)
ary # => ["Show", "\"Harry \\\"Handcuff\\\" Houdini", " the one and only\"", "Tampa Theater"]
puts ary[1] # => "Harry \"Handcuff\" Houdini

使用 liberal_parsing 及其 backslash_quote 子选项

ary = CSV.parse_line(str, liberal_parsing: { backslash_quote: true })
ary # => ["Show", "Harry \"Handcuff\" Houdini, the one and only", "Tampa Theater"]
puts ary[1] # => Harry "Handcuff" Houdini, the one and only
选项 nil_value

指定要替换每个空(无文本)字段的对象。

默认值

CSV::DEFAULT_OPTIONS.fetch(:nil_value) # => nil

使用默认值 nil

CSV.parse_line('a,,b,,c') # => ["a", nil, "b", nil, "c"]

使用不同的对象

CSV.parse_line('a,,b,,c', nil_value: 0) # => ["a", 0, "b", 0, "c"]
选项 empty_value

指定要替换每个包含空字符串的字段的对象。

默认值

CSV::DEFAULT_OPTIONS.fetch(:empty_value) # => "" (empty string)

使用默认值 ""

CSV.parse_line('a,"",b,"",c') # => ["a", "", "b", "", "c"]

使用不同的对象

CSV.parse_line('a,"",b,"",c', empty_value: 'x') # => ["a", "x", "b", "x", "c"]

生成选项

下面详细描述的生成选项包括

选项 row_sep

指定行分隔符(字符串或符号 :auto(见下文))用于解析和生成。

默认值

CSV::DEFAULT_OPTIONS.fetch(:row_sep) # => :auto

row_sep 为字符串时,该字符串将成为行分隔符。String 在使用前将被转码为数据的 Encoding

使用 "\n"

row_sep = "\n"
str = CSV.generate(row_sep: row_sep) do |csv|
  csv << [:foo, 0]
  csv << [:bar, 1]
  csv << [:baz, 2]
end
str # => "foo,0\nbar,1\nbaz,2\n"
ary = CSV.parse(str)
ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]

使用 |(管道)

row_sep = '|'
str = CSV.generate(row_sep: row_sep) do |csv|
  csv << [:foo, 0]
  csv << [:bar, 1]
  csv << [:baz, 2]
end
str # => "foo,0|bar,1|baz,2|"
ary = CSV.parse(str, row_sep: row_sep)
ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]

使用 --(两个连字符)

row_sep = '--'
str = CSV.generate(row_sep: row_sep) do |csv|
  csv << [:foo, 0]
  csv << [:bar, 1]
  csv << [:baz, 2]
end
str # => "foo,0--bar,1--baz,2--"
ary = CSV.parse(str, row_sep: row_sep)
ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]

使用 ''(空字符串)

row_sep = ''
str = CSV.generate(row_sep: row_sep) do |csv|
  csv << [:foo, 0]
  csv << [:bar, 1]
  csv << [:baz, 2]
end
str # => "foo,0bar,1baz,2"
ary = CSV.parse(str, row_sep: row_sep)
ary # => [["foo", "0bar", "1baz", "2"]]

row_sep 是符号 :auto(默认值)时,生成会使用 "\n" 作为行分隔符

str = CSV.generate do |csv|
  csv << [:foo, 0]
  csv << [:bar, 1]
  csv << [:baz, 2]
end
str # => "foo,0\nbar,1\nbaz,2\n"

另一方面,解析会调用行分隔符的自动发现。

自动发现会提前读取数据,寻找下一个 \r\n\n\r 序列。即使该序列出现在带引号的字段中,也会选择该序列,假设您会在那里有相同的行尾。

示例

str = CSV.generate do |csv|
  csv << [:foo, 0]
  csv << [:bar, 1]
  csv << [:baz, 2]
end
str # => "foo,0\nbar,1\nbaz,2\n"
ary = CSV.parse(str)
ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]

如果以下任一条件为真,则使用默认的 $INPUT_RECORD_SEPARATOR$/

显然,发现需要一点时间。如果速度很重要,请手动 Set。另请注意,如果要使用此功能,应在 Windows 上以二进制模式打开 IO 对象,因为行尾转换可能会导致将文档位置重置为读取前的位置时出现问题。

选项 col_sep

指定用于解析和生成的字符串字段分隔符。该字符串在使用前将被转码为数据的编码。

默认值

CSV::DEFAULT_OPTIONS.fetch(:col_sep) # => "," (comma)

使用默认值(逗号)

str = CSV.generate do |csv|
  csv << [:foo, 0]
  csv << [:bar, 1]
  csv << [:baz, 2]
end
str # => "foo,0\nbar,1\nbaz,2\n"
ary = CSV.parse(str)
ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]

使用 :(冒号)

col_sep = ':'
str = CSV.generate(col_sep: col_sep) do |csv|
  csv << [:foo, 0]
  csv << [:bar, 1]
  csv << [:baz, 2]
end
str # => "foo:0\nbar:1\nbaz:2\n"
ary = CSV.parse(str, col_sep: col_sep)
ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]

使用 ::(两个冒号)

col_sep = '::'
str = CSV.generate(col_sep: col_sep) do |csv|
  csv << [:foo, 0]
  csv << [:bar, 1]
  csv << [:baz, 2]
end
str # => "foo::0\nbar::1\nbaz::2\n"
ary = CSV.parse(str, col_sep: col_sep)
ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]

使用 ''(空字符串)

col_sep = ''
str = CSV.generate(col_sep: col_sep) do |csv|
  csv << [:foo, 0]
  csv << [:bar, 1]
  csv << [:baz, 2]
end
str # => "foo0\nbar1\nbaz2\n"

使用空字符串解析时引发异常

col_sep = ''
# Raises ArgumentError (:col_sep must be 1 or more characters: "")
CSV.parse("foo0\nbar1\nbaz2\n", col_sep: col_sep)
选项 quote_char

指定用于在解析和生成中引用字段的字符(长度为 1 的字符串)。此 String 在使用前将被转码为数据的编码。

默认值

CSV::DEFAULT_OPTIONS.fetch(:quote_char) # => "\"" (double quote)

这对于错误地使用 '(单引号)而不是正确的 "(双引号)引用字段的应用程序很有用。

使用默认值(双引号)

str = CSV.generate do |csv|
  csv << ['foo', 0]
  csv << ["'bar'", 1]
  csv << ['"baz"', 2]
end
str # => "foo,0\n'bar',1\n\"\"\"baz\"\"\",2\n"
ary = CSV.parse(str)
ary # => [["foo", "0"], ["'bar'", "1"], ["\"baz\"", "2"]]

使用 '(单引号)

quote_char = "'"
str = CSV.generate(quote_char: quote_char) do |csv|
  csv << ['foo', 0]
  csv << ["'bar'", 1]
  csv << ['"baz"', 2]
end
str # => "foo,0\n'''bar''',1\n\"baz\",2\n"
ary = CSV.parse(str, quote_char: quote_char)
ary # => [["foo", "0"], ["'bar'", "1"], ["\"baz\"", "2"]]

如果字符串长度大于 1,则引发异常

# Raises ArgumentError (:quote_char has to be nil or a single character String)
CSV.new('', quote_char: 'xx')

如果值不是字符串,则引发异常

# Raises ArgumentError (:quote_char has to be nil or a single character String)
CSV.new('', quote_char: :foo)
选项 write_headers

指定确定输出中是否包含标题行的布尔值;如果没有标题,则忽略。

默认值

CSV::DEFAULT_OPTIONS.fetch(:write_headers) # => nil

不使用 write_headers

file_path = 't.csv'
CSV.open(file_path,'w',
    :headers => ['Name','Value']
  ) do |csv|
    csv << ['foo', '0']
end
CSV.open(file_path) do |csv|
  csv.shift
end # => ["foo", "0"]

使用 write_headers

CSV.open(file_path,'w',
    :write_headers => true,
    :headers => ['Name','Value']
  ) do |csv|
    csv << ['foo', '0']
end
CSV.open(file_path) do |csv|
  csv.shift
end # => ["Name", "Value"]
选项 force_quotes

指定确定是否对每个输出字段使用双引号的布尔值。

默认值

CSV::DEFAULT_OPTIONS.fetch(:force_quotes) # => false

有关本部分中的示例

ary = ['foo', 0, nil]

使用默认值 false

str = CSV.generate_line(ary)
str # => "foo,0,\n"

使用 true

str = CSV.generate_line(ary, force_quotes: true)
str # => "\"foo\",\"0\",\"\"\n"
选项 quote_empty

指定确定是否对空值使用双引号的布尔值。

默认值

CSV::DEFAULT_OPTIONS.fetch(:quote_empty) # => true

使用默认值 true

CSV.generate_line(['"', ""]) # => "\"\"\"\",\"\"\n"

使用 false

CSV.generate_line(['"', ""], quote_empty: false) # => "\"\"\"\",\n"
选项 write_converters

指定在生成字段时要使用的转换器。请参阅 写入转换器

默认值

CSV::DEFAULT_OPTIONS.fetch(:write_converters) # => nil

不使用写入转换器

str = CSV.generate_line(["\na\n", "\tb\t", " c "])
str # => "\"\na\n\",\tb\t, c \n"

使用写入转换器

strip_converter = proc {|field| field.strip }
str = CSV.generate_line(["\na\n", "\tb\t", " c "], write_converters: strip_converter)
str # => "a,b,c\n"

使用两个写入转换器(按顺序调用)

upcase_converter = proc {|field| field.upcase }
downcase_converter = proc {|field| field.downcase }
write_converters = [upcase_converter, downcase_converter]
str = CSV.generate_line(['a', 'b', 'c'], write_converters: write_converters)
str # => "a,b,c\n"

另请参阅 写入转换器

选项 write_nil_value

指定要替换每个 nil 值字段的对象。

默认值

CSV::DEFAULT_OPTIONS.fetch(:write_nil_value) # => nil

不使用该选项

str = CSV.generate_line(['a', nil, 'c', nil])
str # => "a,,c,\n"

使用该选项

str = CSV.generate_line(['a', nil, 'c', nil], write_nil_value: "x")
str # => "a,x,c,x\n"
选项 write_empty_value

指定要替换每个包含空字符串的字段的对象。

默认值

CSV::DEFAULT_OPTIONS.fetch(:write_empty_value) # => ""

不使用该选项

str = CSV.generate_line(['a', '', 'c', ''])
str # => "a,\"\",c,\"\"\n"

使用该选项

str = CSV.generate_line(['a', '', 'c', ''], write_empty_value: "x")
str # => "a,x,c,x\n"

带标题的 CSV

CSV 允许指定 CSV 文件的列名,无论它们是在数据中还是单独提供的。如果指定了标题,则读取方法将返回 CSV::Table 的实例,该实例由 CSV::Row 组成。

# Headers are part of data
data = CSV.parse(<<~ROWS, headers: true)
  Name,Department,Salary
  Bob,Engineering,1000
  Jane,Sales,2000
  John,Management,5000
ROWS

data.class      #=> CSV::Table
data.first      #=> #<CSV::Row "Name":"Bob" "Department":"Engineering" "Salary":"1000">
data.first.to_h #=> {"Name"=>"Bob", "Department"=>"Engineering", "Salary"=>"1000"}

# Headers provided by developer
data = CSV.parse('Bob,Engineering,1000', headers: %i[name department salary])
data.first      #=> #<CSV::Row name:"Bob" department:"Engineering" salary:"1000">

转换器

默认情况下,CSV 解析的每个值(字段或标题)都形成一个字符串。你可以使用字段转换器标题转换器来拦截和修改解析的值

同样,默认情况下,在生成期间要写入的每个值都将“按原样”写入。你可以使用写入转换器在写入之前修改值。

指定转换器

您可以在各种 CSV 方法的 options 参数中指定用于解析或生成的转换器

有三种形式可用于指定转换器

转换器 Proc

此转换器 proc strip_converter 接受一个值 field 并返回 field.strip

strip_converter = proc {|field| field.strip }

在此对 CSV.parse 的调用中,关键字参数 converters: string_converter 指定

示例

string = " foo , 0 \n bar , 1 \n baz , 2 \n"
array = CSV.parse(string, converters: strip_converter)
array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]

转换器 proc 可以接收第二个参数 field_info,其中包含有关该字段的详细信息。此修改后的 strip_converter 显示其参数

strip_converter = proc do |field, field_info|
  p [field, field_info]
  field.strip
end
string = " foo , 0 \n bar , 1 \n baz , 2 \n"
array = CSV.parse(string, converters: strip_converter)
array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]

输出

[" foo ", #<struct CSV::FieldInfo index=0, line=1, header=nil>]
[" 0 ", #<struct CSV::FieldInfo index=1, line=1, header=nil>]
[" bar ", #<struct CSV::FieldInfo index=0, line=2, header=nil>]
[" 1 ", #<struct CSV::FieldInfo index=1, line=2, header=nil>]
[" baz ", #<struct CSV::FieldInfo index=0, line=3, header=nil>]
[" 2 ", #<struct CSV::FieldInfo index=1, line=3, header=nil>]

每个 CSV::FieldInfo 对象显示

存储的转换器

可以为转换器指定名称并将其存储在解析方法可以按名称找到它的结构中。

字段转换器的存储结构是哈希 CSV::Converters。它具有多个内置转换器 proc

. 此示例创建一个转换器 proc,然后将其存储

strip_converter = proc {|field| field.strip }
CSV::Converters[:strip] = strip_converter

然后,解析方法调用可以通过其名称 :strip 引用转换器

string = " foo , 0 \n bar , 1 \n baz , 2 \n"
array = CSV.parse(string, converters: :strip)
array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]

头转换器的存储结构是哈希 CSV::HeaderConverters,其工作方式相同。它还内置了转换器过程

对于写入头,没有这样的存储结构。

为了让解析方法能够访问非主 Ractor 中的存储转换器,必须首先使存储结构可共享。因此,必须在使用存储在这些结构中的转换器的 Ractor 创建之前调用 Ractor.make_shareable(CSV::Converters)Ractor.make_shareable(CSV::HeaderConverters)。(由于使存储结构可共享涉及冻结它们,因此必须首先添加要使用的任何自定义转换器。)

转换器列表

转换器列表是一个数组,其中可能包含任何组合的

示例

numeric_converters = [:integer, :float]
date_converters = [:date, :date_time]
[numeric_converters, strip_converter]
[strip_converter, date_converters, :float]

与转换器过程类似,转换器列表可以命名并存储在 CSV::Converters 或 CSV::HeaderConverters

CSV::Converters[:custom] = [strip_converter, date_converters, :float]
CSV::HeaderConverters[:custom] = [:downcase, :symbol]

有两个内置转换器列表

CSV::Converters[:numeric] # => [:integer, :float]
CSV::Converters[:all] # => [:date_time, :numeric]

字段转换器

如果不进行转换,所有行中的所有已解析字段都将变为字符串

string = "foo,0\nbar,1\nbaz,2\n"
ary = CSV.parse(string)
ary # => # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]

指定字段转换器时,每个已解析字段都会传递给转换器;其返回值将成为字段的存储值。例如,转换器可能将嵌入在字符串中的整数转换为真正的整数。(事实上,内置字段转换器 :integer 就是这么做的。)

有三种方法可以使用字段转换器。

安装字段转换器不会影响已读取的行

csv = CSV.new(string)
csv.shift # => ["foo", "0"]
# Add a converter.
csv.convert(:integer)
csv.converters # => [:integer]
csv.read # => [["bar", 1], ["baz", 2]]

还有其他内置转换器,并且还支持自定义转换器。

内置字段转换器

内置字段转换器位于哈希 CSV::Converters

显示

CSV::Converters.each_pair do |name, value|
  if value.kind_of?(Proc)
    p [name, value.class]
  else
    p [name, value]
  end
end

输出

[:integer, Proc]
[:float, Proc]
[:numeric, [:integer, :float]]
[:date, Proc]
[:date_time, Proc]
[:all, [:date_time, :numeric]]

在尝试转换之前,所有这些转换器都会将值转码为 UTF-8。如果无法将值转码为 UTF-8,则转换将失败,并且值将保持未转换状态。

转换器 :integer 转换 Integer() 接受的每个字段

data = '0,1,2,x'
# Without the converter
csv = CSV.parse_line(data)
csv # => ["0", "1", "2", "x"]
# With the converter
csv = CSV.parse_line(data, converters: :integer)
csv # => [0, 1, 2, "x"]

转换器 :float 转换 Float() 接受的每个字段

data = '1.0,3.14159,x'
# Without the converter
csv = CSV.parse_line(data)
csv # => ["1.0", "3.14159", "x"]
# With the converter
csv = CSV.parse_line(data, converters: :float)
csv # => [1.0, 3.14159, "x"]

转换器 :numeric 使用 :integer:float 进行转换。

转换器 :date 转换 Date::parse 接受的每个字段

data = '2001-02-03,x'
# Without the converter
csv = CSV.parse_line(data)
csv # => ["2001-02-03", "x"]
# With the converter
csv = CSV.parse_line(data, converters: :date)
csv # => [#<Date: 2001-02-03 ((2451944j,0s,0n),+0s,2299161j)>, "x"]

转换器 :date_time 转换 DateTime::parse 接受的每个字段

data = '2020-05-07T14:59:00-05:00,x'
# Without the converter
csv = CSV.parse_line(data)
csv # => ["2020-05-07T14:59:00-05:00", "x"]
# With the converter
csv = CSV.parse_line(data, converters: :date_time)
csv # => [#<DateTime: 2020-05-07T14:59:00-05:00 ((2458977j,71940s,0n),-18000s,2299161j)>, "x"]

转换器 :numeric 同时使用 :date_time:numeric 进行转换。

如上所示,方法 convert 向 CSV 实例添加转换器,方法 converters 返回一个有效的转换器数组

csv = CSV.new('0,1,2')
csv.converters # => []
csv.convert(:integer)
csv.converters # => [:integer]
csv.convert(:date)
csv.converters # => [:integer, :date]
自定义字段转换器

你可以定义一个自定义字段转换器

strip_converter = proc {|field| field.strip }
string = " foo , 0 \n bar , 1 \n baz , 2 \n"
array = CSV.parse(string, converters: strip_converter)
array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]

你可以在转换器 Hash 中注册转换器,这样你就可以通过名称引用它

CSV::Converters[:strip] = strip_converter
string = " foo , 0 \n bar , 1 \n baz , 2 \n"
array = CSV.parse(string, converters: :strip)
array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]

标题转换器

标题转换器仅对标题(而不是其他行)进行操作。

有三种方法可以使用标题转换器;这些示例使用内置标题转换器 :downcase,它将每个解析的标题变为小写。

内置标题转换器

内置标题转换器在 Hash CSV::HeaderConverters 中。那里的键是转换器的名称

CSV::HeaderConverters.keys # => [:downcase, :symbol]

转换器 :downcase 通过将每个标题变为小写来转换

string = "Name,Count\nFoo,0\n,Bar,1\nBaz,2"
tbl = CSV.parse(string, headers: true, header_converters: :downcase)
tbl.class # => CSV::Table
tbl.headers # => ["name", "count"]

转换器 :symbol 通过将每个标题转换为符号来转换

string = "Name,Count\nFoo,0\n,Bar,1\nBaz,2"
tbl = CSV.parse(string, headers: true, header_converters: :symbol)
tbl.headers # => [:name, :count]

详细信息

自定义标题转换器

你可以定义一个自定义标题转换器

upcase_converter = proc {|header| header.upcase }
string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
table = CSV.parse(string, headers: true, header_converters: upcase_converter)
table # => #<CSV::Table mode:col_or_row row_count:4>
table.headers # => ["NAME", "VALUE"]

你可以在 HeaderConverters Hash 中注册转换器,这样你就可以通过名称引用它

CSV::HeaderConverters[:upcase] = upcase_converter
table = CSV.parse(string, headers: true, header_converters: :upcase)
table # => #<CSV::Table mode:col_or_row row_count:4>
table.headers # => ["NAME", "VALUE"]
写入转换器

当您为生成 CSV 指定一个写入转换器时,每个要写入的字段都会传递给转换器;它的返回值成为字段的新值。例如,转换器可能会从字段中去除空格。

不使用写入转换器(所有字段未修改)

output_string = CSV.generate do |csv|
  csv << [' foo ', 0]
  csv << [' bar ', 1]
  csv << [' baz ', 2]
end
output_string # => " foo ,0\n bar ,1\n baz ,2\n"

使用选项 write_converters 和两个自定义写入转换器

strip_converter = proc {|field| field.respond_to?(:strip) ? field.strip : field }
upcase_converter = proc {|field| field.respond_to?(:upcase) ? field.upcase : field }
write_converters = [strip_converter, upcase_converter]
output_string = CSV.generate(write_converters: write_converters) do |csv|
  csv << [' foo ', 0]
  csv << [' bar ', 1]
  csv << [' baz ', 2]
end
output_string # => "FOO,0\nBAR,1\nBAZ,2\n"

字符编码(M17n 或多语言化)

这个新的 CSV 解析器精通 m17n。解析器在正在读写 IOString 对象的 Encoding 中工作。您的数据永远不会被转码(除非您要求 Ruby 为您转码),并且将以其所在的 Encoding 进行解析。因此,CSV 将返回 Encoding 中的字符串数组或行。这是通过将解析器本身转码到您的 Encoding 中来实现的。

当然,为了实现这种多编码支持,必须进行一些转码。例如,:col_sep:row_sep:quote_char 必须转码以匹配您的数据。希望这会让整个过程感觉透明,因为 CSV 的默认值应该只对您的数据起作用。但是,您可以在目标 Encoding 中手动设置这些值以避免翻译。

同样重要的是要注意,虽然 CSV 的所有核心解析器现在都是 Encoding 不可知的,但有些特性不是。例如,内置转换器在进行转换之前会尝试将数据转码为 UTF-8。同样,您可以提供了解您的编码的自定义转换器以避免这种翻译。对于我来说,在 Ruby 的所有编码中支持本机转换实在是太难了。

无论如何,这方面的实际情况很简单:确保传递给 IOString 对象的 CSV 具有正确的 Encoding 设置,一切应该都能正常工作。允许您打开 IO 对象的 CSV 方法(CSV::foreach()CSV::open()CSV::read()CSV::readlines())允许您指定 Encoding

当使用与 ASCII 不兼容的 EncodingCSV 生成到 String 时,会产生一个轻微的异常。没有现有的数据供 CSV 用来准备自身,因此您可能需要在大多数情况下手动指定所需的 Encoding。不过,在使用 CSV::generate_line() 或 Array#to_csv() 时,它将尝试使用输出行中的字段进行猜测。

我尝试在方法文档中指出任何其他 Encoding 问题,因为它们会不断出现。

我已经尽我所能使用 Ruby 附带的所有非“虚拟”编码对本代码进行了测试。但是,这是一段新颖的代码,可能存在一些错误。如果您发现任何问题,请随时 报告

常量

ConverterEncoding

所有转换器使用的编码。

转换器

包含内置字段转换器的名称和过程的哈希。请参阅 内置字段转换器

此哈希故意保留未冻结状态,并且可以使用自定义字段转换器进行扩展。请参阅 自定义字段转换器

DEFAULT_OPTIONS

方法选项的默认值。

DateMatcher

用于查找和转换一些常见 Date 格式的 Regexp

DateTimeMatcher

用于查找和转换一些常见 DateTime 格式的 Regexp

FieldInfo

一个 FieldInfo Struct 包含有关字段在其读取到的数据源中的位置的详细信息。 CSV 会将此 Struct 传递给根据字段结构做出决策的一些块。有关示例,请参阅 CSV.convert_fields()

index

字段在其行中的零基索引。

此行所在的数据源行。

标题

列的标题(如果可用)。

已引用?

真或假,原始值是否已引用。

HeaderConverters

包含内置标题转换器的名称和 Procs 的哈希。请参阅内置标题转换器

此哈希故意保留为未冻结状态,并且可以使用自定义字段转换器进行扩展。请参阅自定义标题转换器

版本

已安装库的版本。

属性

编码[R]

:call-seq

csv.encoding -> encoding

返回用于解析和生成的编码;请参阅字符编码(M17n 或多语言化)

CSV.new('').encoding # => #<Encoding:UTF-8>

公共类方法

filter(in_string_or_io, **options) {|row| ... } → array_of_arrays or csv_table 单击以切换源
filter(in_string_or_io, out_string_or_io, **options) {|row| ... } → array_of_arrays or csv_table
filter(**options) {|row| ... } → array_of_arrays or csv_table
  • 从源(String、IO 流或ARGF)解析 CSV。

  • 使用每个已解析行调用给定的块

    • 如果没有标题,则每行都是一个数组。

    • 如果有标题,则每行都是一个CSV::Row

  • 生成 CSV 到输出(String、IO 流或 STDOUT)。

  • 返回已解析的源

    • 如果没有标题,则返回数组的数组。

    • 如果有标题,则返回CSV::Table

当给定in_string_or_io但未给定out_string_or_io时,从给定的in_string_or_io解析并生成到 STDOUT。

没有标题的字符串输入

in_string = "foo,0\nbar,1\nbaz,2"
CSV.filter(in_string) do |row|
  row[0].upcase!
  row[1] = - row[1].to_i
end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]]

输出(到 STDOUT)

FOO,0
BAR,-1
BAZ,-2

带有标题的字符串输入

in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2"
CSV.filter(in_string, headers: true) do |row|
  row[0].upcase!
  row[1] = - row[1].to_i
end # => #<CSV::Table mode:col_or_row row_count:4>

输出(到 STDOUT)

Name,Value
FOO,0
BAR,-1
BAZ,-2

没有标题的 IO 流输入

File.write('t.csv', "foo,0\nbar,1\nbaz,2")
File.open('t.csv') do |in_io|
  CSV.filter(in_io) do |row|
    row[0].upcase!
    row[1] = - row[1].to_i
  end
end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]]

输出(到 STDOUT)

FOO,0
BAR,-1
BAZ,-2

带有标题的 IO 流输入

File.write('t.csv', "Name,Value\nfoo,0\nbar,1\nbaz,2")
File.open('t.csv') do |in_io|
  CSV.filter(in_io, headers: true) do |row|
    row[0].upcase!
    row[1] = - row[1].to_i
  end
end # => #<CSV::Table mode:col_or_row row_count:4>

输出(到 STDOUT)

Name,Value
FOO,0
BAR,-1
BAZ,-2

in_string_or_ioout_string_or_io同时给定时,从in_string_or_io解析并生成到out_string_or_io

没有标题的字符串输出

in_string = "foo,0\nbar,1\nbaz,2"
out_string = ''
CSV.filter(in_string, out_string) do |row|
  row[0].upcase!
  row[1] = - row[1].to_i
end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]]
out_string # => "FOO,0\nBAR,-1\nBAZ,-2\n"

带有标题的字符串输出

in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2"
out_string = ''
CSV.filter(in_string, out_string, headers: true) do |row|
  row[0].upcase!
  row[1] = - row[1].to_i
end # => #<CSV::Table mode:col_or_row row_count:4>
out_string # => "Name,Value\nFOO,0\nBAR,-1\nBAZ,-2\n"

没有标题的 IO 流输出

in_string = "foo,0\nbar,1\nbaz,2"
File.open('t.csv', 'w') do |out_io|
  CSV.filter(in_string, out_io) do |row|
    row[0].upcase!
    row[1] = - row[1].to_i
  end
end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]]
File.read('t.csv') # => "FOO,0\nBAR,-1\nBAZ,-2\n"

带有标题的 IO 流输出

in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2"
File.open('t.csv', 'w') do |out_io|
  CSV.filter(in_string, out_io, headers: true) do |row|
    row[0].upcase!
    row[1] = - row[1].to_i
  end
end # => #<CSV::Table mode:col_or_row row_count:4>
File.read('t.csv') # => "Name,Value\nFOO,0\nBAR,-1\nBAZ,-2\n"

当既未给定in_string_or_io也未给定out_string_or_io时,从ARGF解析并生成到 STDOUT。

没有标题

# Put Ruby code into a file.
ruby = <<-EOT
  require 'csv'
  CSV.filter do |row|
    row[0].upcase!
    row[1] = - row[1].to_i
  end
EOT
File.write('t.rb', ruby)
# Put some CSV into a file.
File.write('t.csv', "foo,0\nbar,1\nbaz,2")
# Run the Ruby code with CSV filename as argument.
system(Gem.ruby, "t.rb", "t.csv")

输出(到 STDOUT)

FOO,0
BAR,-1
BAZ,-2

带有标题

# Put Ruby code into a file.
ruby = <<-EOT
  require 'csv'
  CSV.filter(headers: true) do |row|
    row[0].upcase!
    row[1] = - row[1].to_i
  end
EOT
File.write('t.rb', ruby)
# Put some CSV into a file.
File.write('t.csv', "Name,Value\nfoo,0\nbar,1\nbaz,2")
# Run the Ruby code with CSV filename as argument.
system(Gem.ruby, "t.rb", "t.csv")

输出(到 STDOUT)

Name,Value
FOO,0
BAR,-1
BAZ,-2

参数

  • 参数in_string_or_io必须是 String 或 IO 流。

  • 参数out_string_or_io必须是 String 或 IO 流。

  • 参数**options必须是关键字选项。请参阅解析选项

# File lib/csv.rb, line 1202
def filter(input=nil, output=nil, **options)
  # parse options for input, output, or both
  in_options, out_options = Hash.new, {row_sep: InputRecordSeparator.value}
  options.each do |key, value|
    case key
    when /\Ain(?:put)?_(.+)\Z/
      in_options[$1.to_sym] = value
    when /\Aout(?:put)?_(.+)\Z/
      out_options[$1.to_sym] = value
    else
      in_options[key]  = value
      out_options[key] = value
    end
  end

  # build input and output wrappers
  input  = new(input  || ARGF, **in_options)
  output = new(output || $stdout, **out_options)

  # process headers
  need_manual_header_output =
    (in_options[:headers] and
     out_options[:headers] == true and
     out_options[:write_headers])
  if need_manual_header_output
    first_row = input.shift
    if first_row
      if first_row.is_a?(Row)
        headers = first_row.headers
        yield headers
        output << headers
      end
      yield first_row
      output << first_row
    end
  end

  # read, yield, write
  input.each do |row|
    yield row
    output << row
  end
end
foreach(path_or_io, mode='r', **options) {|row| ... ) 单击以切换源
foreach(path_or_io, mode='r', **options) → new_enumerator

使用从源path_or_io读取的每行调用块。

没有标题的路径输入

string = "foo,0\nbar,1\nbaz,2\n"
in_path = 't.csv'
File.write(in_path, string)
CSV.foreach(in_path) {|row| p row }

输出

["foo", "0"]
["bar", "1"]
["baz", "2"]

带有标题的路径输入

string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
in_path = 't.csv'
File.write(in_path, string)
CSV.foreach(in_path, headers: true) {|row| p row }

输出

<CSV::Row "Name":"foo" "Value":"0">
<CSV::Row "Name":"bar" "Value":"1">
<CSV::Row "Name":"baz" "Value":"2">

没有标题的 IO 流输入

string = "foo,0\nbar,1\nbaz,2\n"
path = 't.csv'
File.write(path, string)
File.open('t.csv') do |in_io|
  CSV.foreach(in_io) {|row| p row }
end

输出

["foo", "0"]
["bar", "1"]
["baz", "2"]

带有标题的 IO 流输入

string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
path = 't.csv'
File.write(path, string)
File.open('t.csv') do |in_io|
  CSV.foreach(in_io, headers: true) {|row| p row }
end

输出

<CSV::Row "Name":"foo" "Value":"0">
<CSV::Row "Name":"bar" "Value":"1">
<CSV::Row "Name":"baz" "Value":"2">

未给定块时,返回枚举器

string = "foo,0\nbar,1\nbaz,2\n"
path = 't.csv'
File.write(path, string)
CSV.foreach(path) # => #<Enumerator: CSV:foreach("t.csv", "r")>

参数

  • 参数 path_or_io 必须是文件路径或 IO 流。

  • 参数 mode(如果已给定)必须是文件模式。请参阅 访问模式

  • 参数**options必须是关键字选项。请参阅解析选项

  • 此方法可以选择接受一个附加的 :encoding 选项,你可以使用该选项指定从 pathio 读取的数据的 Encoding。你必须提供此选项,除非你的数据采用 Encoding::default_external 给出的编码。解析将使用此选项来确定如何解析数据。你可以提供第二个 Encoding,以便在读取数据时对数据进行转码。例如,

    encoding: 'UTF-32BE:UTF-8'

    将从文件中读取 UTF-32BE 数据,但在解析之前将其转码为 UTF-8

# File lib/csv.rb, line 1332
def foreach(path, mode="r", **options, &block)
  return to_enum(__method__, path, mode, **options) unless block_given?
  open(path, mode, **options) do |csv|
    csv.each(&block)
  end
end
generate(csv_string, **options) {|csv| ... } 单击以切换源
generate(**options) {|csv| ... }
  • 参数 csv_string(如果已给定)必须是字符串对象;默认为一个新的空字符串。

  • 参数 options(如果已给定)应为生成选项。请参阅 生成选项


通过 CSV.new(csv_string, **options) 创建新的 CSV 对象;使用 CSV 对象调用块,块可能会修改 CSV 对象;返回从 CSV 对象生成的字符串。

请注意,此方法修改传递的字符串。如果必须保留字符串,请传递 csv_string.dup。

此方法有一个附加选项::encoding,如果未指定 str,则该选项将设置输出的基本 Encoding。如果你计划输出与非 ASCII 兼容的数据,则 CSV 需要此提示。


添加行

input_string = "foo,0\nbar,1\nbaz,2\n"
output_string = CSV.generate(input_string) do |csv|
  csv << ['bat', 3]
  csv << ['bam', 4]
end
output_string # => "foo,0\nbar,1\nbaz,2\nbat,3\nbam,4\n"
input_string # => "foo,0\nbar,1\nbaz,2\nbat,3\nbam,4\n"
output_string.equal?(input_string) # => true # Same string, modified

将行添加到新字符串中,保留旧字符串

input_string = "foo,0\nbar,1\nbaz,2\n"
output_string = CSV.generate(input_string.dup) do |csv|
  csv << ['bat', 3]
  csv << ['bam', 4]
end
output_string # => "foo,0\nbar,1\nbaz,2\nbat,3\nbam,4\n"
input_string # => "foo,0\nbar,1\nbaz,2\n"
output_string.equal?(input_string) # => false # Different strings

从无中创建行

output_string = CSV.generate do |csv|
  csv << ['foo', 0]
  csv << ['bar', 1]
  csv << ['baz', 2]
end
output_string # => "foo,0\nbar,1\nbaz,2\n"

如果 csv_string 不是字符串对象,则引发异常

# Raises TypeError (no implicit conversion of Integer into String)
CSV.generate(0)
# File lib/csv.rb, line 1398
def generate(str=nil, **options)
  encoding = options[:encoding]
  # add a default empty String, if none was given
  if str
    str = StringIO.new(str)
    str.seek(0, IO::SEEK_END)
    str.set_encoding(encoding) if encoding
  else
    str = +""
    str.force_encoding(encoding) if encoding
  end
  csv = new(str, **options) # wrap
  yield csv         # yield for appending
  csv.string        # return final String
end
generate_line(ary) 单击以切换源
generate_line(ary, **options)

返回通过使用指定 optionsary 生成 CSV 创建的字符串。

参数 ary 必须是数组。

特殊选项

  • 选项 :row_sep 在 Ruby 3.0 或更高版本中默认为 "\n">,在其他版本中默认为 <tt>$INPUT_RECORD_SEPARATOR$/)。

    $INPUT_RECORD_SEPARATOR # => "\n"
    
  • 此方法接受一个附加选项 :encoding,该选项设置输出的基本 Encoding。如果可能,此方法将尝试从 row 中的第一个非 nil 字段猜测你的 Encoding,但你可能需要将此参数用作备用计划。

有关其他选项,请参阅生成选项


返回从数组生成的字符串

CSV.generate_line(['foo', '0']) # => "foo,0\n"

如果ary不是数组,则引发异常

# Raises NoMethodError (undefined method `find' for :foo:Symbol)
CSV.generate_line(:foo)
# File lib/csv.rb, line 1446
def generate_line(row, **options)
  options = {row_sep: InputRecordSeparator.value}.merge(options)
  str = +""
  if options[:encoding]
    str.force_encoding(options[:encoding])
  else
    fallback_encoding = nil
    output_encoding = nil
    row.each do |field|
      next unless field.is_a?(String)
      fallback_encoding ||= field.encoding
      next if field.ascii_only?
      output_encoding = field.encoding
      break
    end
    output_encoding ||= fallback_encoding
    if output_encoding
      str.force_encoding(output_encoding)
    end
  end
  (new(str, **options) << row).string
end
generate_lines(rows) 单击以切换源
generate_lines(rows, **options)

返回使用指定的选项从 CSV 生成的字符串。

参数rows必须是行的数组。 Row 是字符串或 CSV::Row 的数组。

特殊选项

  • 选项:row_sep在 Ruby 3.0 或更高版本上默认为"\n",否则默认为$INPUT_RECORD_SEPARATOR ($/)。

    $INPUT_RECORD_SEPARATOR # => "\n"
    
  • 此方法接受一个附加选项 :encoding,该选项设置输出的基本 Encoding。如果可能,此方法将尝试从 row 中的第一个非 nil 字段猜测你的 Encoding,但你可能需要将此参数用作备用计划。

有关其他选项,请参阅生成选项


返回从

CSV.generate_lines([['foo', '0'], ['bar', '1'], ['baz', '2']]) # => "foo,0\nbar,1\nbaz,2\n"

引发异常

# Raises NoMethodError (undefined method `each' for :foo:Symbol)
CSV.generate_lines(:foo)
# File lib/csv.rb, line 1501
def generate_lines(rows, **options)
  self.generate(**options) do |csv|
    rows.each do |row|
      csv << row
    end
  end
end
instance(string, **options) 单击以切换源
instance(io = $stdout, **options)
instance(string, **options) {|csv| ... }
instance(io = $stdout, **options) {|csv| ... }

创建或检索缓存的 CSV 对象。有关参数和选项,请参阅CSV.new

此 API 不支持 Ractor。


如果没有给定块,则返回 CSV 对象。

instance的首次调用将创建一个 CSV 对象并将其缓存

s0 = 's0'
csv0 = CSV.instance(s0)
csv0.class # => CSV

使用相同的stringioinstance的后续调用将检索相同的缓存对象

csv1 = CSV.instance(s0)
csv1.class # => CSV
csv1.equal?(csv0) # => true # Same CSV object

使用不同的stringioinstance的后续调用将创建一个不同的 CSV 对象并将其缓存。

s1 = 's1'
csv2 = CSV.instance(s1)
csv2.equal?(csv0) # => false # Different CSV object

所有缓存的对象都保持可用

csv3 = CSV.instance(s0)
csv3.equal?(csv0) # true # Same CSV object
csv4 = CSV.instance(s1)
csv4.equal?(csv2) # true # Same CSV object

当给定一个块时,使用创建或检索的 CSV 对象调用该块;返回块的返回值

CSV.instance(s0) {|csv| :foo } # => :foo
# File lib/csv.rb, line 1006
def instance(data = $stdout, **options)
  # create a _signature_ for this method call, data object and options
  sig = [data.object_id] +
        options.values_at(*DEFAULT_OPTIONS.keys)

  # fetch or create the instance for this signature
  @@instances ||= Hash.new
  instance = (@@instances[sig] ||= new(data, **options))

  if block_given?
    yield instance  # run block, if given, returning result
  else
    instance        # or return the instance
  end
end
new(string) 单击以切换源
new(io)
new(string, **options)
new(io, **options)

返回使用stringio和指定的选项创建的新 CSV 对象。

  • 参数string应为 String 对象;它将被放入一个新的 StringIO 对象中,该对象位于开头。

  • 参数io应为 IO 对象,该对象

    • 打开以供读取;返回时,IO 对象将被关闭。

    • 位于开头。要定位到末尾以进行追加,请使用 CSV.generate 方法。对于任何其他定位,请改而传递一个预设的 StringIO 对象。

  • 参数options:请参阅

    出于性能原因,选项无法在 CSV 对象中被覆盖,因此此处指定的选项将持续存在。

除了 CSV 实例方法外,还委派了多个 IO 方法。请参阅 委派方法


从 String 对象创建 CSV 对象

csv = CSV.new('foo,0')
csv # => #<CSV io_type:StringIO encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">

从 File 对象创建 CSV 对象

File.write('t.csv', 'foo,0')
csv = CSV.new(File.open('t.csv'))
csv # => #<CSV io_type:File io_path:"t.csv" encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">

如果参数为 nil,则引发异常

# Raises ArgumentError (Cannot parse nil as CSV):
CSV.new(nil)
# File lib/csv.rb, line 1905
def initialize(data,
               col_sep: ",",
               row_sep: :auto,
               quote_char: '"',
               field_size_limit: nil,
               max_field_size: nil,
               converters: nil,
               unconverted_fields: nil,
               headers: false,
               return_headers: false,
               write_headers: nil,
               header_converters: nil,
               skip_blanks: false,
               force_quotes: false,
               skip_lines: nil,
               liberal_parsing: false,
               internal_encoding: nil,
               external_encoding: nil,
               encoding: nil,
               nil_value: nil,
               empty_value: "",
               strip: false,
               quote_empty: true,
               write_converters: nil,
               write_nil_value: nil,
               write_empty_value: "")
  raise ArgumentError.new("Cannot parse nil as CSV") if data.nil?

  if data.is_a?(String)
    if encoding
      if encoding.is_a?(String)
        data_external_encoding, data_internal_encoding = encoding.split(":", 2)
        if data_internal_encoding
          data = data.encode(data_internal_encoding, data_external_encoding)
        else
          data = data.dup.force_encoding(data_external_encoding)
        end
      else
        data = data.dup.force_encoding(encoding)
      end
    end
    @io = StringIO.new(data)
  else
    @io = data
  end
  @encoding = determine_encoding(encoding, internal_encoding)

  @base_fields_converter_options = {
    nil_value: nil_value,
    empty_value: empty_value,
  }
  @write_fields_converter_options = {
    nil_value: write_nil_value,
    empty_value: write_empty_value,
  }
  @initial_converters = converters
  @initial_header_converters = header_converters
  @initial_write_converters = write_converters

  if max_field_size.nil? and field_size_limit
    max_field_size = field_size_limit - 1
  end
  @parser_options = {
    column_separator: col_sep,
    row_separator: row_sep,
    quote_character: quote_char,
    max_field_size: max_field_size,
    unconverted_fields: unconverted_fields,
    headers: headers,
    return_headers: return_headers,
    skip_blanks: skip_blanks,
    skip_lines: skip_lines,
    liberal_parsing: liberal_parsing,
    encoding: @encoding,
    nil_value: nil_value,
    empty_value: empty_value,
    strip: strip,
  }
  @parser = nil
  @parser_enumerator = nil
  @eof_error = nil

  @writer_options = {
    encoding: @encoding,
    force_encoding: (not encoding.nil?),
    force_quotes: force_quotes,
    headers: headers,
    write_headers: write_headers,
    column_separator: col_sep,
    row_separator: row_sep,
    quote_character: quote_char,
    quote_empty: quote_empty,
  }

  @writer = nil
  writer if @writer_options[:write_headers]
end
open(file_path, mode = "rb", **options ) → new_csv 单击以切换源
open(io, mode = "rb", **options ) → new_csv
open(file_path, mode = "rb", **options ) { |csv| ... } → object
open(io, mode = "rb", **options ) { |csv| ... } → object

可能的选项元素

keyword form:
  :invalid => nil      # raise error on invalid byte sequence (default)
  :invalid => :replace # replace invalid byte sequence
  :undef => :replace   # replace undefined conversion
  :replace => string   # replacement string ("?" or "\uFFFD" if not specified)
  • 如果给定参数 path,它必须是文件的路径。

  • 参数io应为 IO 对象,该对象

    • 打开以供读取;返回时,IO 对象将被关闭。

    • 位于开头。要定位到末尾以进行追加,请使用 CSV.generate 方法。对于任何其他定位,请改而传递一个预设的 StringIO 对象。

  • 参数 mode(如果已给定)必须是文件模式。请参阅 访问模式

  • 参数 **options 必须是关键字选项。请参阅 生成选项

  • 此方法可以选择接受一个附加的 :encoding 选项,你可以使用该选项指定从 pathio 读取的数据的 Encoding。你必须提供此选项,除非你的数据采用 Encoding::default_external 给出的编码。解析将使用此选项来确定如何解析数据。你可以提供第二个 Encoding,以便在读取数据时对数据进行转码。例如,

    encoding: 'UTF-32BE:UTF-8'

    将从文件中读取 UTF-32BE 数据,但在解析之前将其转码为 UTF-8


以下示例假定事先执行了

string = "foo,0\nbar,1\nbaz,2\n"
path = 't.csv'
File.write(path, string)

如果没有给定块,则返回一个新的 CSV 对象。

使用文件路径创建 CSV 对象

csv = CSV.open(path)
csv # => #<CSV io_type:File io_path:"t.csv" encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">

使用打开的文件创建 CSV 对象

csv = CSV.open(File.open(path))
csv # => #<CSV io_type:File io_path:"t.csv" encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">

如果给定块,则使用创建的 CSV 对象调用该块;返回该块的返回值

使用文件路径

csv = CSV.open(path) {|csv| p csv}
csv # => #<CSV io_type:File io_path:"t.csv" encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">

输出

#<CSV io_type:File io_path:"t.csv" encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">

使用打开的文件

csv = CSV.open(File.open(path)) {|csv| p csv}
csv # => #<CSV io_type:File io_path:"t.csv" encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">

输出

#<CSV io_type:File io_path:"t.csv" encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">

如果参数不是 String 对象或 IO 对象,则引发异常

# Raises TypeError (no implicit conversion of Symbol into String)
CSV.open(:foo)
# File lib/csv.rb, line 1581
def open(filename, mode="r", **options)
  # wrap a File opened with the remaining +args+ with no newline
  # decorator
  file_opts = options.dup
  unless file_opts.key?(:newline)
    file_opts[:universal_newline] ||= false
  end
  options.delete(:invalid)
  options.delete(:undef)
  options.delete(:replace)
  options.delete_if {|k, _| /newline\z/.match?(k)}

  begin
    f = File.open(filename, mode, **file_opts)
  rescue ArgumentError => e
    raise unless /needs binmode/.match?(e.message) and mode == "r"
    mode = "rb"
    file_opts = {encoding: Encoding.default_external}.merge(file_opts)
    retry
  end
  begin
    csv = new(f, **options)
  rescue Exception
    f.close
    raise
  end

  # handle blocks like Ruby's open(), not like the CSV library
  if block_given?
    begin
      yield csv
    ensure
      csv.close
    end
  else
    csv
  end
end
parse(string) → array_of_arrays 单击以切换源
parse(io) → array_of_arrays
parse(string, headers: ..., **options) → csv_table
parse(io, headers: ..., **options) → csv_table
parse(string, **options) {|row| ... }
parse(io, **options) {|row| ... }

使用指定的 options 解析 stringio

  • 参数string应为 String 对象;它将被放入一个新的 StringIO 对象中,该对象位于开头。

  • 参数io应为 IO 对象,该对象

    • 打开以供读取;返回时,IO 对象将被关闭。

    • 位于开头。要定位到末尾以进行追加,请使用 CSV.generate 方法。对于任何其他定位,请改而传递一个预设的 StringIO 对象。

  • 参数 options:请参阅 解析选项

无选项 headers

无 {option headers} 的情况。

以下示例假定事先执行了

string = "foo,0\nbar,1\nbaz,2\n"
path = 't.csv'
File.write(path, string)

如果没有给定块,则从源返回一个由数组组成的数组。

解析一个 String

a_of_a = CSV.parse(string)
a_of_a # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]

解析一个打开的文件

a_of_a = File.open(path) do |file|
  CSV.parse(file)
end
a_of_a # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]

如果给定块,则使用每个已解析的行调用该块

解析一个 String

CSV.parse(string) {|row| p row }

输出

["foo", "0"]
["bar", "1"]
["baz", "2"]

解析一个打开的文件

File.open(path) do |file|
  CSV.parse(file) {|row| p row }
end

输出

["foo", "0"]
["bar", "1"]
["baz", "2"]
有选项 headers

带有 {option headers} 的情况。

以下示例假定事先执行了

string = "Name,Count\nfoo,0\nbar,1\nbaz,2\n"
path = 't.csv'
File.write(path, string)

如果没有给定代码块,则返回一个由源形成的 CSV::Table 对象。

解析一个 String

csv_table = CSV.parse(string, headers: ['Name', 'Count'])
csv_table # => #<CSV::Table mode:col_or_row row_count:5>

解析一个打开的文件

csv_table = File.open(path) do |file|
  CSV.parse(file, headers: ['Name', 'Count'])
end
csv_table # => #<CSV::Table mode:col_or_row row_count:4>

如果给定了代码块,则使用每个已解析行调用该代码块,该行已形成为 CSV::Row 对象

解析一个 String

CSV.parse(string, headers: ['Name', 'Count']) {|row| p row }

输出

# <CSV::Row "Name":"foo" "Count":"0">
# <CSV::Row "Name":"bar" "Count":"1">
# <CSV::Row "Name":"baz" "Count":"2">

解析一个打开的文件

File.open(path) do |file|
  CSV.parse(file, headers: ['Name', 'Count']) {|row| p row }
end

输出

# <CSV::Row "Name":"foo" "Count":"0">
# <CSV::Row "Name":"bar" "Count":"1">
# <CSV::Row "Name":"baz" "Count":"2">

如果参数不是 String 对象或 IO 对象,则引发异常

# Raises NoMethodError (undefined method `close' for :foo:Symbol)
CSV.parse(:foo)
# File lib/csv.rb, line 1732
def parse(str, **options, &block)
  csv = new(str, **options)

  return csv.each(&block) if block_given?

  # slurp contents, if no block is given
  begin
    csv.read
  ensure
    csv.close
  end
end
parse_line(string) → new_array or nil 单击以切换源
parse_line(io) → new_array or nil
parse_line(string, **options) → new_array or nil
parse_line(io, **options) → new_array or nil
parse_line(string, headers: true, **options) → csv_row or nil
parse_line(io, headers: true, **options) → csv_row or nil

返回通过使用指定的 options 解析 stringio 的第一行创建的数据。

  • 参数string应为 String 对象;它将被放入一个新的 StringIO 对象中,该对象位于开头。

  • 参数io应为 IO 对象,该对象

    • 打开以供读取;返回时,IO 对象将被关闭。

    • 位于开头。要定位到末尾以进行追加,请使用 CSV.generate 方法。对于任何其他定位,请改而传递一个预设的 StringIO 对象。

  • 参数 options:请参阅 解析选项

没有 Option headers

如果没有 headers 选项,则返回第一行作为新的数组。

以下示例假定事先执行了

string = "foo,0\nbar,1\nbaz,2\n"
path = 't.csv'
File.write(path, string)

从 String 对象解析第一行

CSV.parse_line(string) # => ["foo", "0"]

File 对象解析第一行

File.open(path) do |file|
  CSV.parse_line(file) # => ["foo", "0"]
end # => ["foo", "0"]

如果参数是空 String,则返回 nil

CSV.parse_line('') # => nil
带有 Option headers

带有 {option headers},返回第一行作为 CSV::Row 对象。

以下示例假定事先执行了

string = "Name,Count\nfoo,0\nbar,1\nbaz,2\n"
path = 't.csv'
File.write(path, string)

从 String 对象解析第一行

CSV.parse_line(string, headers: true) # => #<CSV::Row "Name":"foo" "Count":"0">

File 对象解析第一行

File.open(path) do |file|
  CSV.parse_line(file, headers: true)
end # => #<CSV::Row "Name":"foo" "Count":"0">

如果参数为 nil,则引发异常

# Raises ArgumentError (Cannot parse nil as CSV):
CSV.parse_line(nil)
# File lib/csv.rb, line 1805
def parse_line(line, **options)
  new(line, **options).each.first
end
read(source, **options) → array_of_arrays 单击以切换源
read(source, headers: true, **options) → csv_table

使用给定的 options(参见 CSV.open)打开给定的 source,读取源(参见 CSV#read),并返回结果,结果将是数组的数组或 CSV::Table

没有标题

string = "foo,0\nbar,1\nbaz,2\n"
path = 't.csv'
File.write(path, string)
CSV.read(path) # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]

带有标题

string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
path = 't.csv'
File.write(path, string)
CSV.read(path, headers: true) # => #<CSV::Table mode:col_or_row row_count:4>
# File lib/csv.rb, line 1829
def read(path, **options)
  open(path, **options) { |csv| csv.read }
end
readlines(source, **options) 单击以切换源

别名 CSV.read

# File lib/csv.rb, line 1837
def readlines(path, **options)
  read(path, **options)
end
table(source, **options) 单击以切换源

使用 sourceoptions 和某些默认选项调用 CSV.read

  • headers: true

  • converters: :numeric

  • header_converters: :symbol

返回 CSV::Table 对象。

示例

string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
path = 't.csv'
File.write(path, string)
CSV.table(path) # => #<CSV::Table mode:col_or_row row_count:4>
# File lib/csv.rb, line 1856
def table(path, **options)
  default_options = {
    headers:           true,
    converters:        :numeric,
    header_converters: :symbol,
  }
  options = default_options.merge(options)
  read(path, **options)
end

公共实例方法

csv << row → self 单击以切换源

将一行追加到 self

  • 参数 row 必须是 Array 对象或 CSV::Row 对象。

  • 输出流必须打开用于写入。


追加数组

CSV.generate do |csv|
  csv << ['foo', 0]
  csv << ['bar', 1]
  csv << ['baz', 2]
end # => "foo,0\nbar,1\nbaz,2\n"

追加 CSV::Rows

headers = []
CSV.generate do |csv|
  csv << CSV::Row.new(headers, ['foo', 0])
  csv << CSV::Row.new(headers, ['bar', 1])
  csv << CSV::Row.new(headers, ['baz', 2])
end # => "foo,0\nbar,1\nbaz,2\n"

CSV::Row 对象中的标题不会被追加

headers = ['Name', 'Count']
CSV.generate do |csv|
  csv << CSV::Row.new(headers, ['foo', 0])
  csv << CSV::Row.new(headers, ['bar', 1])
  csv << CSV::Row.new(headers, ['baz', 2])
end # => "foo,0\nbar,1\nbaz,2\n"

如果row不是数组或 CSV::Row,则引发异常

CSV.generate do |csv|
  # Raises NoMethodError (undefined method `collect' for :foo:Symbol)
  csv << :foo
end

如果输出流未打开以进行写入,则引发异常

path = 't.csv'
File.write(path, '')
File.open(path) do |file|
  CSV.open(file) do |csv|
    # Raises IOError (not opened for writing)
    csv << ['foo', 0]
  end
end
# File lib/csv.rb, line 2372
def <<(row)
  writer << row
  self
end
别名:add_rowputs
add_row
别名:<<
binmode?() 单击以切换源
# File lib/csv.rb, line 2261
def binmode?
  if @io.respond_to?(:binmode?)
    @io.binmode?
  else
    false
  end
end
col_sep → string 单击以切换源

返回编码的列分隔符;用于解析和写入;请参见{Option col_sep}

CSV.new('').col_sep # => ","
# File lib/csv.rb, line 2009
def col_sep
  parser.column_separator
end
convert(converter_name) → array_of_procs 单击以切换源
convert {|field, field_info| ... } → array_of_procs
  • 如果没有块,则安装字段转换器(Proc)。

  • 使用块,定义并安装自定义字段转换器。

  • 返回已安装字段转换器的数组。

  • 如果给出了参数converter_name,则它应该是现有字段转换器的名称。

请参阅 字段转换器


如果没有块,则安装字段转换器

csv = CSV.new('')
csv.convert(:integer)
csv.convert(:float)
csv.convert(:date)
csv.converters # => [:integer, :float, :date]

如果给出了块,则对每个字段调用该块

  • 参数field是字段值。

  • 参数field_info是一个CSV::FieldInfo对象,其中包含有关该字段的详细信息。

此处的示例假定先前执行了

string = "foo,0\nbar,1\nbaz,2\n"
path = 't.csv'
File.write(path, string)

给出一个块的示例

csv = CSV.open(path)
csv.convert {|field, field_info| p [field, field_info]; field.upcase }
csv.read # => [["FOO", "0"], ["BAR", "1"], ["BAZ", "2"]]

输出

["foo", #<struct CSV::FieldInfo index=0, line=1, header=nil>]
["0", #<struct CSV::FieldInfo index=1, line=1, header=nil>]
["bar", #<struct CSV::FieldInfo index=0, line=2, header=nil>]
["1", #<struct CSV::FieldInfo index=1, line=2, header=nil>]
["baz", #<struct CSV::FieldInfo index=0, line=3, header=nil>]
["2", #<struct CSV::FieldInfo index=1, line=3, header=nil>]

该块不必返回 String 对象

csv = CSV.open(path)
csv.convert {|field, field_info| field.to_sym }
csv.read # => [[:foo, :"0"], [:bar, :"1"], [:baz, :"2"]]

如果给出了converter_name,则不会调用该块

csv = CSV.open(path)
csv.convert(:integer) {|field, field_info| fail 'Cannot happen' }
csv.read # => [["foo", 0], ["bar", 1], ["baz", 2]]

如果converter_name不是内置字段转换器的名称,则引发解析时异常

csv = CSV.open(path)
csv.convert(:nosuch) => [nil]
# Raises NoMethodError (undefined method `arity' for nil:NilClass)
csv.read
# File lib/csv.rb, line 2443
def convert(name = nil, &converter)
  parser_fields_converter.add_converter(name, &converter)
end
converters → array 单击以切换源

返回包含字段转换器的数组;请参见字段转换器

csv = CSV.new('')
csv.converters # => []
csv.convert(:integer)
csv.converters # => [:integer]
csv.convert(proc {|x| x.to_s })
csv.converters

请注意,您需要在主Ractor上调用+Ractor.make_shareable(CSV::Converters)+才能使用此方法。

# File lib/csv.rb, line 2082
def converters
  parser_fields_converter.map do |converter|
    name = Converters.rassoc(converter)
    name ? name.first : converter
  end
end
each → enumerator 单击以切换源
each {|row| ...}

使用每一行调用该块。数据源必须打开以进行读取。

没有标题

string = "foo,0\nbar,1\nbaz,2\n"
csv = CSV.new(string)
csv.each do |row|
  p row
end

输出

["foo", "0"]
["bar", "1"]
["baz", "2"]

带有标题

string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
csv = CSV.new(string, headers: true)
csv.each do |row|
  p row
end

输出

<CSV::Row "Name":"foo" "Value":"0">
<CSV::Row "Name":"bar" "Value":"1">
<CSV::Row "Name":"baz" "Value":"2">

如果源未打开以进行读取,则引发异常

string = "foo,0\nbar,1\nbaz,2\n"
csv = CSV.new(string)
csv.close
# Raises IOError (not opened for reading)
csv.each do |row|
  p row
end
# File lib/csv.rb, line 2554
def each(&block)
  return to_enum(__method__) unless block_given?
  begin
    while true
      yield(parser_enumerator.next)
    end
  rescue StopIteration
  end
end
eof()
别名:eof?
eof?() 点击切换源代码
# File lib/csv.rb, line 2297
def eof?
  return false if @eof_error
  begin
    parser_enumerator.peek
    false
  rescue MalformedCSVError => error
    @eof_error = error
    false
  rescue StopIteration
    true
  end
end
别名:eof
field_size_limit → integer 或 nil 点击切换源代码

返回字段大小限制;用于解析;参见 {Option field_size_limit}

CSV.new('').field_size_limit # => nil

自 3.2.3 起已弃用。请改用 max_field_size

# File lib/csv.rb, line 2041
def field_size_limit
  parser.field_size_limit
end
flock(*args) 点击切换源代码
# File lib/csv.rb, line 2269
def flock(*args)
  raise NotImplementedError unless @io.respond_to?(:flock)
  @io.flock(*args)
end
force_quotes? → true 或 false 点击切换源代码

返回一个值,该值决定是否对所有输出字段加上引号;用于生成;参见 {Option force_quotes}

CSV.new('').force_quotes? # => false
# File lib/csv.rb, line 2172
def force_quotes?
  @writer_options[:force_quotes]
end
gets
别名:shift
header_convert(name = nil, &converter) 点击切换源代码

该块不必返回 String 对象

csv = CSV.open(path, headers: true)
csv.header_convert {|header, field_info| header.to_sym }
table = csv.read
table.headers # => [:Name, :Value]

如果给出了converter_name,则不会调用该块

csv = CSV.open(path, headers: true)
csv.header_convert(:downcase) {|header, field_info| fail 'Cannot happen' }
table = csv.read
table.headers # => ["name", "value"]

如果converter_name不是内置字段转换器的名称,则引发解析时异常

csv = CSV.open(path, headers: true)
csv.header_convert(:nosuch)
# Raises NoMethodError (undefined method `arity' for nil:NilClass)
csv.read
# File lib/csv.rb, line 2509
def header_convert(name = nil, &converter)
  header_fields_converter.add_converter(name, &converter)
end
header_converters → array 点击切换源代码

返回一个包含标题转换器的数组;用于解析;参见 标题转换器

CSV.new('').header_converters # => []

请注意,您需要在主 Ractor 上调用 +Ractor.make_shareable(CSV::HeaderConverters)+ 才能使用此方法。

# File lib/csv.rb, line 2148
def header_converters
  header_fields_converter.map do |converter|
    name = HeaderConverters.rassoc(converter)
    name ? name.first : converter
  end
end
header_row? → true 或 false 点击切换源代码

如果要读取的下一行是标题行,则返回 true;否则返回 false

没有标题

string = "foo,0\nbar,1\nbaz,2\n"
csv = CSV.new(string)
csv.header_row? # => false

带有标题

string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
csv = CSV.new(string, headers: true)
csv.header_row? # => true
csv.shift # => #<CSV::Row "Name":"foo" "Value":"0">
csv.header_row? # => false

如果源未打开以进行读取,则引发异常

string = "foo,0\nbar,1\nbaz,2\n"
csv = CSV.new(string)
csv.close
# Raises IOError (not opened for reading)
csv.header_row?
# File lib/csv.rb, line 2631
def header_row?
  parser.header_row?
end
headers → object 点击切换源代码

返回一个值,该值决定是否使用标题;用于解析;参见 {Option headers}

CSV.new('').headers # => nil
# File lib/csv.rb, line 2106
def headers
  if @writer
    @writer.headers
  else
    parsed_headers = parser.headers
    return parsed_headers if parsed_headers
    raw_headers = @parser_options[:headers]
    raw_headers = nil if raw_headers == false
    raw_headers
  end
end
inspect → string 点击切换源代码

返回一个字符串,显示 self 的某些属性

string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
csv = CSV.new(string, headers: true)
s = csv.inspect
s # => "#<CSV io_type:StringIO encoding:UTF-8 lineno:0 col_sep:\",\" row_sep:\"\\n\" quote_char:\"\\\"\" headers:true>"
# File lib/csv.rb, line 2690
def inspect
  str = ["#<", self.class.to_s, " io_type:"]
  # show type of wrapped IO
  if    @io == $stdout then str << "$stdout"
  elsif @io == $stdin  then str << "$stdin"
  elsif @io == $stderr then str << "$stderr"
  else                      str << @io.class.to_s
  end
  # show IO.path(), if available
  if @io.respond_to?(:path) and (p = @io.path)
    str << " io_path:" << p.inspect
  end
  # show encoding
  str << " encoding:" << @encoding.name
  # show other attributes
  ["lineno", "col_sep", "row_sep", "quote_char"].each do |attr_name|
    if a = __send__(attr_name)
      str << " " << attr_name << ":" << a.inspect
    end
  end
  ["skip_blanks", "liberal_parsing"].each do |attr_name|
    if a = __send__("#{attr_name}?")
      str << " " << attr_name << ":" << a.inspect
    end
  end
  _headers = headers
  str << " headers:" << _headers.inspect if _headers
  str << ">"
  begin
    str.join('')
  rescue  # any encoding error
    str.map do |s|
      e = Encoding::Converter.asciicompat_encoding(s.encoding)
      e ? s.encode(e) : s.force_encoding("ASCII-8BIT")
    end.join('')
  end
end
ioctl(*args) 点击切换源代码
# File lib/csv.rb, line 2274
def ioctl(*args)
  raise NotImplementedError unless @io.respond_to?(:ioctl)
  @io.ioctl(*args)
end
liberal_parsing? → true 或 false 点击切换源代码

返回一个值,该值决定是否处理非法输入;用于解析;参见 {Option liberal_parsing}

CSV.new('').liberal_parsing? # => false
# File lib/csv.rb, line 2182
def liberal_parsing?
  parser.liberal_parsing?
end
line → array 点击切换源代码

返回最近读取的行

string = "foo,0\nbar,1\nbaz,2\n"
path = 't.csv'
File.write(path, string)
CSV.open(path) do |csv|
  csv.each do |row|
    p [csv.lineno, csv.line]
  end
end

输出

[1, "foo,0\n"]
[2, "bar,1\n"]
[3, "baz,2\n"]
# File lib/csv.rb, line 2247
def line
  parser.line
end
line_no → 整数 点击切换源代码

返回已解析或生成的行的计数。

解析

string = "foo,0\nbar,1\nbaz,2\n"
path = 't.csv'
File.write(path, string)
CSV.open(path) do |csv|
  csv.each do |row|
    p [csv.lineno, row]
  end
end

输出

[1, ["foo", "0"]]
[2, ["bar", "1"]]
[3, ["baz", "2"]]

生成

CSV.generate do |csv|
  p csv.lineno; csv << ['foo', 0]
  p csv.lineno; csv << ['bar', 1]
  p csv.lineno; csv << ['baz', 2]
end

输出

0
1
2
# File lib/csv.rb, line 2223
def lineno
  if @writer
    @writer.lineno
  else
    parser.lineno
  end
end
max_field_size → 整数或 nil 点击切换源代码

返回字段大小限制;用于解析;参见 {Option max_field_size}

CSV.new('').max_field_size # => nil

自 3.2.3 起。

# File lib/csv.rb, line 2053
def max_field_size
  parser.max_field_size
end
path() 点击切换源代码
# File lib/csv.rb, line 2279
def path
  @io.path if @io.respond_to?(:path)
end
puts
别名:<<
quote_char → 字符 点击切换源代码

返回编码的引用字符;用于解析和写入;参见 {Option quote_char}

CSV.new('').quote_char # => "\""
# File lib/csv.rb, line 2029
def quote_char
  parser.quote_character
end
read → 数组或 csv_table 点击切换源代码

self 中的剩余行组成

  • 如果使用标题,则为 CSV::Table 对象。

  • 否则为数组的数组。

数据源必须打开才能读取。

没有标题

string = "foo,0\nbar,1\nbaz,2\n"
path = 't.csv'
File.write(path, string)
csv = CSV.open(path)
csv.read # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]

带有标题

string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
path = 't.csv'
File.write(path, string)
csv = CSV.open(path, headers: true)
csv.read # => #<CSV::Table mode:col_or_row row_count:4>

如果源未打开以进行读取,则引发异常

string = "foo,0\nbar,1\nbaz,2\n"
csv = CSV.new(string)
csv.close
# Raises IOError (not opened for reading)
csv.read
# File lib/csv.rb, line 2595
def read
  rows = to_a
  if parser.use_headers?
    Table.new(rows, headers: parser.headers)
  else
    rows
  end
end
别名:readlines
readline
别名:shift
readlines
别名:read
return_headers? → true 或 false 点击切换源代码

返回确定是否返回标题的值;用于解析;参见 {Option return_headers}

CSV.new('').return_headers? # => false
# File lib/csv.rb, line 2124
def return_headers?
  parser.return_headers?
end
rewind() 点击切换源代码

倒带底层 IO 对象并重置 CSV 的 lineno() 计数器。

# File lib/csv.rb, line 2312
def rewind
  @parser = nil
  @parser_enumerator = nil
  @eof_error = nil
  @writer.rewind if @writer
  @io.rewind
end
row_sep → 字符串 点击切换源代码

返回编码的行分隔符;用于解析和写入;参见 {Option row_sep}

CSV.new('').row_sep # => "\n"
# File lib/csv.rb, line 2019
def row_sep
  parser.row_separator
end
shift → 数组、csv_row 或 nil 点击切换源代码

返回下一行数据,形式为

  • 如果不使用标题,则为数组。

  • 如果使用标题,则为 CSV::Row 对象。

数据源必须打开才能读取。

没有标题

string = "foo,0\nbar,1\nbaz,2\n"
csv = CSV.new(string)
csv.shift # => ["foo", "0"]
csv.shift # => ["bar", "1"]
csv.shift # => ["baz", "2"]
csv.shift # => nil

带有标题

string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
csv = CSV.new(string, headers: true)
csv.shift # => #<CSV::Row "Name":"foo" "Value":"0">
csv.shift # => #<CSV::Row "Name":"bar" "Value":"1">
csv.shift # => #<CSV::Row "Name":"baz" "Value":"2">
csv.shift # => nil

如果源未打开以进行读取,则引发异常

string = "foo,0\nbar,1\nbaz,2\n"
csv = CSV.new(string)
csv.close
# Raises IOError (not opened for reading)
csv.shift
# File lib/csv.rb, line 2668
def shift
  if @eof_error
    eof_error, @eof_error = @eof_error, nil
    raise eof_error
  end
  begin
    parser_enumerator.next
  rescue StopIteration
    nil
  end
end
别名:getsreadline
skip_blanks? → true 或 false 点击切换源代码

返回一个值,该值确定是否忽略空行;用于解析;参见 {Option skip_blanks}

CSV.new('').skip_blanks? # => false
# File lib/csv.rb, line 2161
def skip_blanks?
  parser.skip_blanks?
end
skip_lines → 正则表达式或 nil 点击切换源代码

返回用于识别注释行的正则表达式;用于解析;参见 {Option skip_lines}

CSV.new('').skip_lines # => nil
# File lib/csv.rb, line 2063
def skip_lines
  parser.skip_lines
end
stat(*args) 点击切换源代码
# File lib/csv.rb, line 2283
def stat(*args)
  raise NotImplementedError unless @io.respond_to?(:stat)
  @io.stat(*args)
end
to_i() 点击切换源代码
# File lib/csv.rb, line 2288
def to_i
  raise NotImplementedError unless @io.respond_to?(:to_i)
  @io.to_i
end
to_io() 点击切换源代码
# File lib/csv.rb, line 2293
def to_io
  @io.respond_to?(:to_io) ? @io.to_io : @io
end
unconverted_fields? → 对象 点击切换源代码

返回一个值,该值确定是否提供未转换的字段;用于解析;参见 {Option unconverted_fields}

CSV.new('').unconverted_fields? # => nil
# File lib/csv.rb, line 2096
def unconverted_fields?
  parser.unconverted_fields?
end
write_headers? → true 或 false 点击切换源代码

返回一个值,该值确定是否写入标题;用于生成;参见 {Option write_headers}

CSV.new('').write_headers? # => nil
# File lib/csv.rb, line 2134
def write_headers?
  @writer_options[:write_headers]
end

私有实例方法

build_fields_converter(initial_converters, options) 点击切换源代码
# File lib/csv.rb, line 2822
def build_fields_converter(initial_converters, options)
  fields_converter = FieldsConverter.new(options)
  normalize_converters(initial_converters).each do |name, converter|
    fields_converter.add_converter(name, &converter)
  end
  fields_converter
end
build_header_fields_converter() 点击切换源代码
# File lib/csv.rb, line 2804
def build_header_fields_converter
  specific_options = {
    builtin_converters_name: :HeaderConverters,
    accept_nil: true,
  }
  options = @base_fields_converter_options.merge(specific_options)
  build_fields_converter(@initial_header_converters, options)
end
build_parser_fields_converter() 点击切换源代码
# File lib/csv.rb, line 2792
def build_parser_fields_converter
  specific_options = {
    builtin_converters_name: :Converters,
  }
  options = @base_fields_converter_options.merge(specific_options)
  build_fields_converter(@initial_converters, options)
end
build_writer_fields_converter() 点击切换源代码
# File lib/csv.rb, line 2817
def build_writer_fields_converter
  build_fields_converter(@initial_write_converters,
                         @write_fields_converter_options)
end
convert_fields(fields, headers = false) 点击切换源代码

使用 @converters 处理 fields,如果将 headers 作为 true 传递,则使用 @header_converters,返回转换后的字段集。将字段更改为 String 以外内容的任何转换器都会停止该字段的转换管道。这主要是一种效率捷径。

# File lib/csv.rb, line 2767
def convert_fields(fields, headers = false)
  if headers
    header_fields_converter.convert(fields, nil, 0)
  else
    parser_fields_converter.convert(fields, @headers, lineno)
  end
end
determine_encoding(encoding, internal_encoding) 点击切换源代码
# File lib/csv.rb, line 2730
def determine_encoding(encoding, internal_encoding)
  # honor the IO encoding if we can, otherwise default to ASCII-8BIT
  io_encoding = raw_encoding
  return io_encoding if io_encoding

  return Encoding.find(internal_encoding) if internal_encoding

  if encoding
    encoding, = encoding.split(":", 2) if encoding.is_a?(String)
    return Encoding.find(encoding)
  end

  Encoding.default_internal || Encoding.default_external
end
header_fields_converter() 点击切换源代码
# File lib/csv.rb, line 2800
def header_fields_converter
  @header_fields_converter ||= build_header_fields_converter
end
normalize_converters(converters) 点击切换源代码
# File lib/csv.rb, line 2745
def normalize_converters(converters)
  converters ||= []
  unless converters.is_a?(Array)
    converters = [converters]
  end
  converters.collect do |converter|
    case converter
    when Proc # custom code block
      [nil, converter]
    else # by name
      [converter, nil]
    end
  end
end
parser() 单击切换源
# File lib/csv.rb, line 2830
def parser
  @parser ||= Parser.new(@io, parser_options)
end
parser_enumerator() 单击切换源
# File lib/csv.rb, line 2839
def parser_enumerator
  @parser_enumerator ||= parser.parse
end
parser_fields_converter() 单击切换源
# File lib/csv.rb, line 2788
def parser_fields_converter
  @parser_fields_converter ||= build_parser_fields_converter
end
parser_options() 单击切换源
# File lib/csv.rb, line 2834
def parser_options
  @parser_options.merge(header_fields_converter: header_fields_converter,
                        fields_converter: parser_fields_converter)
end
raw_encoding() 单击切换源

返回内部 IO 对象的编码。

# File lib/csv.rb, line 2778
def raw_encoding
  if @io.respond_to? :internal_encoding
    @io.internal_encoding || @io.external_encoding
  elsif @io.respond_to? :encoding
    @io.encoding
  else
    nil
  end
end
writer() 单击切换源
# File lib/csv.rb, line 2843
def writer
  @writer ||= Writer.new(@io, writer_options)
end
writer_fields_converter() 单击切换源
# File lib/csv.rb, line 2813
def writer_fields_converter
  @writer_fields_converter ||= build_writer_fields_converter
end
writer_options() 单击切换源
# File lib/csv.rb, line 2847
def writer_options
  @writer_options.merge(header_fields_converter: header_fields_converter,
                        fields_converter: writer_fields_converter)
end