2008-02-21

CSVを固定長フォーマットのテキストに変換して普通のエディタで開く

文字コードとか考えてません。最初 gawk で書いてたけど match() でマッチした文字列を配列で受け取れるgawk拡張について

3.1.5 はバグ持ちだよ

という記述を見つけてしまって凹んでました。というか手元の 3.1.4 で試して動いたんだか動かないんだかよく分からなくて放り投げてしまいました。awk スクリプトで最新版を要求するなんて優しさが足りない。

Therefore use:

echo test4325363test | gawk 'match($0, "([^0-9]*)([0-9]+)(.*)", a) { print a[2] }'

to extract the number.

Please, note that gawk 3.1.5 has some bugs in the match function. These should be corrected in gawk 3.1.6 (see ftp://ftp.gnu.org).

How to gram awk's regexp submatches? - Object Mix

というわけで Ruby に鞍替え。

なんか諸々 CSV に決め打ちで TSV とか対応できませんけど勘弁してちょ。optparse で delimiter 受け取って動的に正規表現作ればイケますよ、とだけ書いてお茶を濁しておく。

途中、範囲式からループを回す処理は見慣れないと気持ち悪いけど、なぜか Ruby を書いていると i += 1 とかカウンタを自分でいじる方が気持ち悪いと感じるようになるのです。for ( ; ; ) 文がないので while の中で自分でカウンタいじることになるんだけど、そうするとインクリメントのタイミングによって値が変わっちゃうとかそういう落とし穴を作ることになるんでやりたくなくなるのです。do end と { } が混ざっているのはごめんなさいということで。

#! /usr/bin/env ruby

class Csv2Fixed
  def initialize
    @num_fields = 0
    @num_lines  = 0
    @len_cols   = []
    @buf        = []
  end

  def run
    while ( line = gets )
      arr = split_csv( line.chomp )
      if ( arr.size > @num_fields )
        @num_fields = arr.size
      end
      store_line( arr )
      @num_lines += 1
    end

    output()
  end

  def store_line( arr )
    @buf[@num_lines] = []

    (0 ... arr.size).each do |i|
      str = arr[i]
      if ( @len_cols[i].nil? or str.size > @len_cols[i] )
        @len_cols[i] = str.size
      end
      @buf[@num_lines].push( str )
    end
  end

  def output
    (0 ... @num_lines).each do |i|
      arr = @buf[i]
      (0 ... arr.size).each do |j|
        printf( "%-*s ", @len_cols[j], @buf[i][j] )
      end
      printf( "\n" )
    end
  end

  def split_csv( str )
    str = str + ","
    arr = []

    str.scan( /(\"(?:[^\"]|\"\")*\"|[^,]*),/ ).each { |e|
      arr.push( e.to_s.sub( /^"/, '' ).sub( /"$/, '' ).sub( /""/, '"' ) )
    }

    return arr
  end
end # of class Csv2Fixed

if ( __FILE__ == $0 )
  app = Csv2Fixed.new()
  app.run()
end

About

例によって個人のなんちゃらです