首頁 > 軟體

Ruby操作CSV格式資料方法詳解

2022-04-18 19:00:29

CSV格式的資料預設是以逗號分隔各個欄位的一條一條記錄,預設用換行符分隔每一條記錄。此外,有的CSV有標題行,有的沒有。還有其他一些格式, 它們都有預設值,但都可以在讀、寫CSV資料時修改預設設定。後文大多數時候故意忽略這些設定,因為絕大多數讀寫操作都使用同樣的引數**options進行格式設定。例如,在讀取csv檔案中的資料時想要忽略標題行,可以在引數中設定headers: true 

可設定的項及其預設值包括:

col_sep: ",",                #=> 欄位分隔符
row_sep: :auto,              #=> 記錄分隔符
quote_char: '"',             #=> 包圍欄位的符號
field_size_limit: nil,       #=> 限制欄位的字元數量
converters: nil,             #=> 
unconverted_fields: nil,
headers: false,              #=> 讀取時忽略標題行,具體參考官方手冊
return_headers: false,
write_headers: nil,
header_converters: nil,
skip_blanks: false,          #=> 忽略空行
force_quotes: false,         #=> 設定為true時,所有欄位都將使用被包圍
skip_lines: nil,             #=> 指定一個正則(str也會轉換為正則),
                             #=> 匹配的行將被當作註釋行而忽略
liberal_parsing: false,
internal_encoding: nil,
external_encoding: nil,
encoding: nil,
nil_value: nil,             #=> 使用此處設定的值替換所有nil欄位
empty_value: "",            #=> 使用此處設定的值替換所有空字串欄位
quote_empty: true,          #=> 設定為false時,空字串欄位將轉換為空欄位
write_converters: nil,
write_nil_value: nil,      #=> 將以此處的值替換nil欄位寫入檔案
write_empty_value: "",
strip: false

CSV類方法處理CSV資料

以CSV格式寫入檔案

要向檔案中寫入CSV格式的資料:

require 'csv'

writer = CSV.open('/tmp/file.csv', 'w')
writer << ["junmajinlong", 29, 170, true]
writer << ["junma", 24, 176, false]
writer << ["jinlong", 25, 172, nil]
writer << ["majinlong", 23, 173, false]
writer.close

寫入完成後,檢視:

junmajinlong,29,170,true
junma,24,176,false
jinlong,25,172,
majinlong,23,173,false

注意其中的nil對應的寫入內容為空。

可以直接在語句塊中寫入,這樣的話可以自動關閉CSV.open()開啟的IO流:

require 'csv'

CSV.open('/tmp/file.csv', 'w') do |writer|
  writer << ["junmajinlong", 29, 170, true]
  writer << ["junma", 24, 176, false]
  writer << ["jinlong", 25, 172, nil]
  writer << ["majinlong", 23, 173, false]
end

CSV.open()開啟的是一個封裝後的IO流物件,它除了可以使用CSV單獨為其提供的一些方法(比如這裡的<<)外,還可以使用很多IO流物件的方法,比如seek()、tell()、flush()、eof?()、fsync()等等。

這裡使用的<<方法是單獨為其提供的,它涉及兩個執行過程:

  • 將陣列中各元素全部轉換成字串型別並使用逗號連線
  • 按行寫入到csv開啟的檔案中

轉換為CSV格式的字串

如果只是想執行第一個過程,即將資料轉換成CSV格式的字串而不寫入,可使用類方法generate_line()

p CSV.generate_line ["junmajinlong", 29, 170, true]
p CSV.generate_line ["jun ma", 24, 176, false]
p CSV.generate_line ["jinlong", 25, 172, nil]
p CSV.generate_line ["jin, long", 23, 173, false]
=begin
"junmajinlong,29,170,truen"
"jun ma,24,176,falsen"
"jinlong,25,172,n"
""jin, long",23,173,falsen"
=end

從CSV格式的檔案中讀資料

如果想要讀取CSV檔案,可使用類方法read()或別名readlines():

pp CSV.readlines('/tmp/file.csv')
=begin
[["junmajinlong", "29", "170", "true"],
 ["junma", "24", "176", "false"],
 ["jinlong", "25", "172", nil],
 ["majinlong", "23", "173", "false"]]
=end

注意:

  • 讀取CSV檔案內容時,每行儲存為一個陣列,每個欄位是這個陣列中的一個元素
  • 讀取CSV檔案內容時,除了不存在的欄位轉換為nil外,其它所有的資料都轉換成了字串型別。所以有時候可能需要去轉換讀取時的資料型別。關於型別轉換,見後文

如果要按行讀取CSV檔案的內容,使用類方法foreach():

CSV.foreach('/tmp/file.csv') do |row|
  p row
end
=begin
["junmajinlong", "29", "170", "true"]
["junma", "24", "176", "false"]
["jinlong", "25", "172", nil]
["majinlong", "23", "173", "false"]
=end

從CSV格式的字串中讀資料

如果想要從字串中讀取CSV格式的資料,使用parse()和parse_line(),分別用於解析多行字串和解析單行字串(超出一行的自動被忽略)。

  • parse()不指定語句塊時,返回包含解析每一行得到的陣列,即一個陣列的陣列,它是一個csv table型別,有很多自己的方法
  • 指定語句塊時,每一行對應的陣列傳遞給語句塊控制變數
str1=<<-eof
junmajinlong,29,170,true
jun ma,24,176,false
jinlong,25,172,
"jin, long",23,173,false
eof

# 不指定語句塊時,parse返回陣列
pp CSV.parse str1
=begin
[["junmajinlong", "29", "170", "true"],
 ["jun ma", "24", "176", "false"],
 ["jinlong", "25", "172", nil],
 ["jin, long", "23", "173", "false"]]
=end

# 指定語句塊時,parse將每行對應的陣列傳遞給語句塊
CSV.parse(str1) {|row| p row}
=begin
["junmajinlong", "29", "170", "true"]
["jun ma", "24", "176", "false"]
["jinlong", "25", "172", nil]
["jin, long", "23", "173", "false"]
=end

str2="junmajinlong,29,170,true"
p CSV.parse_line str2
["junmajinlong", "29", "170", "true"]

CSV實體方法處理CSV資料

  • CSV.new()CSV.open()可以建立csv物件(即一行一行csv格式的資料)
  • CSV.generate()可將字串轉換成csv物件並將該物件傳遞給語句塊
  • <<puts()add_row()可向CSV目標中(字串格式的CSV或CSV IO流)寫入行,它們是別名關係
  • gets()shift()readline()可從csv物件中讀取一行資料
  • read()readlines()可以讀取csv物件中的所有資料
  • each()可以從csv物件中迭代每一行
  • eof()eof?()可以判斷是否讀完所有資料
  • rewind()可以重置當前csv物件的偏移指標
  • line()可以獲取最近一次讀取的一行資料
  • lineno()可以獲取當前已讀取的行數
  • path()可以獲取當前讀取的csv檔名

CSV table

CSV.parse()、CSV.read()、CSV.table()等方法返回的都是陣列的陣列(二維陣列),它們是CSV Table。

CSV table按照表的方式來處理csv資料,比如關注於行、關注於欄位的一些操作可以採用csv table相關的方法來處理。

# 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資料時,所有的資料都會轉換為字串格式。

# Without any converters:
CSV.parse('Bob,2018-03-01,100')
#=> [["Bob", "2018-03-01", "100"]]

可以在迭代每一行的語句塊中對欄位做必要的型別轉換。

但如果型別轉換方式比較簡單,可以在讀取資料時指定converters屬性進行轉換。該屬性的值要麼是CSV的內建型別符號,要麼是符號陣列,要麼是一個lambda表示式。有如下內建型別:

Integer
Float
Numeric (Float + Integer)
Date
DateTime
All

當指定了型別轉換後,每個欄位將針對converters的值嘗試做轉換,轉換失敗則保留欄位的值不變,所以如果通過lambda自定義型別轉換時也一定要保證這一點。

CSV.parse("1,2,3,4,5", converters: :numeric)
#=> [[1, 2, 3, 4, 5]]

# With built-in converters:
ct = CSV.parse('Bob,2018-03-01,100', converters: %i[numeric date])
#=> [["Bob", #<Date: 2018-03-01>, 100]]
ct.first[1] + 1  # 日期物件,加1天
#=> #<Date: 2018-03-02 ((2458180j,0s,0n),+0s,2299161j)>

# With custom converters:
CSV.parse('Bob,2018-03-01,100', converters: [->(v) { Time.parse(v) rescue v }])
#=> [["Bob", 2018-03-01 00:00:00 +0200, "100"]]

更多關於Ruby操作CSV格式資料方法請檢視下面的相關連結


IT145.com E-mail:sddin#qq.com