TL;DR
headers: true
と return_headers: true
を指定すること(write_headers
は指定しないこと)。
ruby -v ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [x86_64-darwin21]
echo "a,b\n1,2" | ruby -r csv -e 'CSV.filter(headers: true, return_headers: true, write_headers: true){ |row| p [row.class, row]}' [Array, ["a", "b"]] a,b [CSV::Row, #<CSV::Row "a":"a" "b":"b">] a,b [CSV::Row, #<CSV::Row "a":"1" "b":"2">] 1,2
write_headers
を特別扱いしているが、これがかえって悪さをしている気がする。
ruby/csv.rb at 5921bfc7ce91aa8079dd8ac4faf873ec911ce320 · ruby/ruby · GitHub
CSV.filter
のブロック内でヘッダ行を CSV::Row
インスタンスとして処理したい場合は return_headers: true
が必要なので、write_headers
を指定しないのが正解。
echo "a,b\n1,2" | ruby -r csv -e 'CSV.filter(headers: true, return_headers: true){ |row| p [row.class, row]}' [CSV::Row, #<CSV::Row "a":"a" "b":"b">] a,b [CSV::Row, #<CSV::Row "a":"1" "b":"2">] 1,2
echo "a,b\n1,2" | ruby -r csv -e 'CSV.filter(headers: true, return_headers: true){ |row| row.header_row? && row[0]=row[0].upcase }' A,b 1,2
return_headers: true
の代わりに write_headers: true
でもヘッダ行はブロックに渡されるが、CSV::Row
インスタンスではなく Array
になるので適切なインスタンスメソッドが使えない。
echo "a,b\n1,2" | ruby -r csv -e 'CSV.filter(headers: true, write_headers: true){ |row| p [row.class, row]}' [Array, ["a", "b"]] a,b [CSV::Row, #<CSV::Row "a":"1" "b":"2">] 1,2
echo "a,b\n1,2" | ruby -r csv -e 'CSV.filter(headers: true, write_headers: true){ |row| row.header_row? && row[0]=row[0].upcase }' -e:1:in `block in <main>': undefined method `header_row?' for ["a", "b"]:Array (NoMethodError) CSV.filter(headers: true, write_headers: true){ |row| row.header_row? && row[0]=row[0].upcase } ^^^^^^^^^^^^ from /Users/kyanny/.rbenv/versions/3.1.2/lib/ruby/3.1.0/csv.rb:1093:in `filter' from -e:1:in `<main>'
CSV.filter (Ruby 3.1 リファレンスマニュアル) のサンプルコードのように return_headers: true
と write_headers: true
を両方指定すると、冒頭の実行例のようにヘッダ行が 2 行出力されるばかりか、最初のヘッダ行は Array
のインスタンスなので適切なインスタンスメソッドが使えない。サンプルコードどおりに実装しても動作せず、ハマる。
echo "a,b\n1,2" | ruby -r csv -e 'CSV.filter(headers: true, return_headers: true, write_headers: true){ |row| row.header_row? && row[0]=row[0].upcase }' -e:1:in `block in <main>': undefined method `header_row?' for ["a", "b"]:Array (NoMethodError) CSV.filter(headers: true, return_headers: true, write_headers: true){ |row| row.header_row? && row[0]=row[0].upcase } ^^^^^^^^^^^^ from /Users/kyanny/.rbenv/versions/3.1.2/lib/ruby/3.1.0/csv.rb:1093:in `filter' from -e:1:in `<main>'
CSV.filter (Ruby 3.1 リファレンスマニュアル) のサンプルコードは他にも間違っていて、CSV.filter
に Hash
インスタンスとして options
を渡しているが、これだとキーワード引数ではなく第一引数と解釈され、第一引数は String
か IO
のインスタンスを期待しているので、実行時エラーになる。
echo "a,b\n1,2" | ruby -e 'require "csv" options = { headers: true, return_headers: true, write_headers: true } CSV.filter(options) do |row| if row.header_row? row << "header3" next end row << "row1_3" end' /Users/kyanny/.rbenv/versions/3.1.2/lib/ruby/3.1.0/csv/parser.rb:237:in `read_chunk': private method `gets' called for {:headers=>true, :return_headers=>true, :write_headers=>true}:Hash (NoMethodError) chunk = input.gets(@row_separator, @chunk_size) ^^^^^ from /Users/kyanny/.rbenv/versions/3.1.2/lib/ruby/3.1.0/csv/parser.rb:95:in `initialize' from /Users/kyanny/.rbenv/versions/3.1.2/lib/ruby/3.1.0/csv/parser.rb:795:in `new' from /Users/kyanny/.rbenv/versions/3.1.2/lib/ruby/3.1.0/csv/parser.rb:795:in `build_scanner' from /Users/kyanny/.rbenv/versions/3.1.2/lib/ruby/3.1.0/csv/parser.rb:332:in `parse' from /Users/kyanny/.rbenv/versions/3.1.2/lib/ruby/3.1.0/csv.rb:2365:in `each' from /Users/kyanny/.rbenv/versions/3.1.2/lib/ruby/3.1.0/csv.rb:2365:in `each' from /Users/kyanny/.rbenv/versions/3.1.2/lib/ruby/3.1.0/csv.rb:1102:in `filter' from -e:5:in `<main>'
**options
と展開しないといけない(それでも前述の Array
インスタンスが渡される問題で実行時エラーになるが)。
echo "a,b\n1,2" | ruby -e 'require "csv" options = { headers: true, return_headers: true, write_headers: true } CSV.filter(**options) do |row| if row.header_row? row << "header3" next end row << "row1_3" end' -e:6:in `block in <main>': undefined method `header_row?' for ["a", "b"]:Array (NoMethodError) if row.header_row? ^^^^^^^^^^^^ from /Users/kyanny/.rbenv/versions/3.1.2/lib/ruby/3.1.0/csv.rb:1093:in `filter' from -e:5:in `<main>'