つよく、やさしく、泥臭く生きていくブログ

日常とポエムと、ときどき技術

Perlは母国語

post.tetsuji.jp

このあたり読んで触発されたので独り言を書く。

僕にとってPerlはプログラム言語の中では母国語なんですよね。なんなら日本語の次に読み書きできる言語といっても過言ではない。
Perlだったらとりあえず何とかできますって感じ。安心感もある。
まあ僕にとっては本当に謙遜でもなんでもなくPerlしかできないってレベルなんですけど。それも、日常会話は問題なくできるけど難しいことは分かんない☆ みたいな。
逆に言うと日常会話レベルなら大丈夫なので、ちょっとしたことはPerlでやりたい。
いいよねPerl。いい意味で適当に書けるし、linux上でだいたいすぐ使えるし。
ちょっとしたjavascript書いてても、「Perlで書きてえ、Perlだったらスラスラかけるのに」みたいなんよくある。
といってもPerlについてはまだまだちゃんと理解したほうがいいことが山ほどあるので、まだまだ付き合っていきたいって感じ。
あとなんか、会社のリポジトリには資産が積みあがってるけど、自分の力として資産が詰みあがってないなあという気持ち。

Perlが古いとか言われるのは、あ、はいそうですねとしか。日本語も古いですし。
Perlが読みにくいとか言われるのも、読みにくいコードは読みにくいですよ。
古文は読めないし、昭和時代の文章でも読みにくいでしょう。
現代語だって、訛りがきつかったら何言ってっか分かんないですし。早口とか、言葉足らずでも分からないのと一緒。
ちゃんと現代の標準語に近い感じで書けばそれなりに読みやすいですし。
誰に言われたか忘れたけど、「Perlは言語だから自然に読めるように書け」と教わりました。
言語なので、同じことを伝えるのにほんと色んな言い方ができるんですよPerlは。
って、Perl以外そんな知らんのに言うのどうなんかなって思わなくもない。

自分のプログラミング遍歴でも書こう。 遍歴っていうほどじゃないんですけど。

HSP

HSPってご存知ですか?僕もよく知りません。
初めてのプログラミングは中学生の時に出会ったHSPでした。
なんでこれを見つけたのかはさて覚えていませんが、当時は僕にとってフリーゲーム全盛期で、自分でも作れる何かを求めて見つけたんじゃないかなあと思います。
時系列の記憶が曖昧ですが、そのとき好きだったフリーゲームの作者さんが、HSPでゲームを作ってたのを覚えています。
変数とか条件分岐とか繰り返しとか、プログラミングのきほんはこれで学びました。
キー入力やクリック入力を取得できることを知って、家族共有PCでキーロガーを仕込みました。陰湿でしたね。
高専入学してからか見学のときか覚えていませんが、プログラミング部に見学に行ったときに「HSP触ってます」って言ったらキョトンとされたのを覚えています。軽くショックでした。

Pascal

教育用の言語?って聞きました。高専1年生でプログラミング基礎みたいなんで使いました。

C言語

高専2年からのプログラミングの授業で使いました。ポインタは分かったけどポインタのポインタが分かりませんでした。
多桁電卓のプログラムを作ったのを覚えています。それはそれで楽しかった。

アセンブリ言語機械語

なんかパチパチやりながら、メモリ番地を指定して、命令を指定して、値を指定して、みたいなやつ 実験でほんのちょっとだけ触った。機械ってすごいなって思いました。

なんか忘れたけどマイコンのやつ

なんか忘れたけどLチカするようなやつ

なんか忘れたけどいろいろ触らしてもらっていい環境だよ高専は(ステマ)

PHP

入社1年目
右も左もPHPSQLも分からないのに、PHPで作られた、なかなかレジェンドな社内基幹システムの軽い(当時としては重い)リファクタリングを命ぜられました。
PHPが分からないなりにプログラミングの基礎知識だけで乗り切りました。

Perl

入社2年目 俺たちのPerlはこれからだ!

Python

今年から。第二外国語

DBのリレーション

人と人の関係も難しいし、DBのリレーションも分からない。
ので基本を確認することにした。
だいたい社内のそういうのって既に良い感じに使えるように作られてて、それを使うかコピペして微修正するだけなので、細かいことはよく分かってなかった。

特に見たいのはリレーションのところで、SQLでいうとjoinの動作?なところ。

  • belongs_to
  • might_have

のあたり

この記事はPerlDBIx::Classなんかを使う感じのやつです。

準備

  • とりあえずMySQLで適当なテーブルを作ります。
  • 人の関係はよく分からないのでプリキュアで例示します。
mysql> use PRECURE;
mysql> SELECT * FROM Series;
+-----------+-----------+
| series_id | name      |
+-----------+-----------+
|         1 | hutariha  |
|         2 | max heart |
|         9 | smile     |
|        15 | hug       |
+-----------+-----------+

mysql> SELECT * FROM Precure;
+------------+-----------+----------------+
| precure_id | series_id | name           |
+------------+-----------+----------------+
|          1 |         1 | black          |
|          2 |         1 | white          |
|          3 |         2 | shiny luminous |
|         55 |        15 | amour          |
|        102 |        13 | mofurun        |
+------------+-----------+----------------+

  • 穴抜けがあるのはあえて。 全部は面倒くさい
  • このテーブルでは問題があるけどとりあえずスルー。

    • ブラックとホワイトはMHにも含まれてるはずだがそれが表現できない
    • レギュラープリキュアに含まれないキュアモフルンのプリキュアIDを100番台に設定しているが、10数年後にはレギュラープリキュアが100人を超えるので困る。
  • とりあえず使えるか確認

# DB接続
use My::Schema;

my $schema =  My::Schema->connect(
    "DBI:mysql:$database",
    $user,
    $password
) or die "cannot connect to MySWL: $DBI::errstr";

# 一人目のプリキュアの名前を取得してみる
my $precure_rs =  $schema->resultset('Precure');
my $precure = $precure_rs->search({
    precure_id => 1,
})->first;

print $precure->name;    # black

基本

  • プリキュア名からシリーズタイトルを取得する
    • 地道にやるとこうなる
# 名前でPrecureテーブルを検索
my $precure = $precure_rs->find({
    name => 'black',
});

# 検索結果の情報からシリーズIDでSeriesテーブルを検索
my $series = $series_rs->find({
    series_id => $precure->series_id,
});

say $series->name;    # hutariha
  • シリーズタイトルからそのシリーズのプリキュアを取得する
    • 地道にやるとこうなる
my $series = $series_rs->find({
    name => 'hutariha',
});

my $precure = $precure_rs->search({
    series_id => $series->series_id,
});

while (my $rs = $precure->next) {
    print $rs->name, "\n";
}

#----
# black
# white

さらにプリキュア名からそのプリキュアの所属するシリーズの全プリキュア名を取るってなると手間が多い

リレーション

  • リレーションを設定すると、前述のことがさらりとかけるようになる。
  • シリーズタイトルからそのシリーズのプリキュアを取得する
my $precure = $series_rs->find({
   name => 'hutariha'
})->precure;

while (my $rs = $precure->next) {
    print $rs->name, "\n";
}
# ----
# black
# white
my $precure = $precure_rs->find({
    name => 'white'
})->series->precure;

while (my $rs = $series->next) {
    print $rs->name, "\n";
}
# ----
# black
# white

リレーションの書き方

スキーマ情報を書いてるファイルにこんな風に追記する。

  • Series.pm
__PACKAGE__->has_many(  # SeriesはPrecure をたくさん持ってる
    # 'precure' はこのリレーションを使うときの名前なので何でもいい
    'precure' => 'DB3::DBIC::Schema::Result::Precure', 
    # 2つのテーブルを紐付ける条件。
    # foreign はここではPrecure。selfがSeries
    {'foreign.series_id' => 'self.series_id'}
);
  • Precure.pm
__PACKAGE__->belongs_to(    # Precureはシリーズに属する
    'series' => 'DB3::DBIC::Schema::Result::Series',
    { 'foreign.series_id' => 'self.series_id'}
);
  • 1対多の関係
    一つのシリーズに複数のプリキュアが居る。
    一人のプリキュアは一つのシリーズに属する。(今はMHや5GoGoは考えない)
    • has_many
      • シリーズ has_many プリキュア
      • これで返ってくるのはResultSetクラス??。many持ってるので当然ながら複数で返ってくる。
      • なので、->first->name したり、->nextでイテレートする。
    • belongs_to
      • プリキュア belongs_to シリーズ
      • これで返ってくるのは、Result::テーブル名 のクラス??。うーんつまりまあ一個だけ返ってくる。
      • なので、そのまま->name できる。
  • 1対1の関係?
    • might_have
      手元でやってるときはSQLを吐かせても、belongs_toとの違いがいまいち分からなかったのだけど、 プロダクションコードでSQLを吐くようにして見てみると、belongs_toのときはJOINで、might_haveのときはLEFT JOINされているのが確認できた。
      具体的に何をどうしたところでそのJOINが出てるのかまでは追えてないが、そんな感じ。
      名前的にも確かに、絶対両方あるよねだからJOINでいいよね、っていうのとまあ多分あるでしょでもないかもだからLEFT JOINにしとくねっていう感じ。

参考

use DBIx::Class; - 今日のCPANモジュール(跡地)

DBIx::Class::Relationship - テーブル間のリレーションシップ

第3回 DBIx::Classでデータベース操作(1):Perl Hackers Hub|gihyo.jp … 技術評論社

JOINによるテーブルの結合方法5種類を整理 - 具体例で学ぶ数学

JOINについて

そもそものSQLでJOINの動作を確認する。 シリーズIDで結合させる。

  • 内部結合ってやつ
mysql > SELECT * FROM Series se INNER JOIN Precure pr ON se.series_id=pr.series_id;
+-----------+-----------+------------+-----------+----------------+
| series_id | name      | precure_id | series_id | name           |
+-----------+-----------+------------+-----------+----------------+
|         1 | hutariha  |          1 |         1 | black          |
|         1 | hutariha  |          2 |         1 | white          |
|         2 | max heart |          3 |         2 | shiny luminous |
|        15 | hug       |         55 |        15 | amour          |
+-----------+-----------+------------+-----------+----------------+

両テーブルにあるものしか出ない。( smile は出ないしモフルンも出ない )

  • 外部結合ってやつ
mysql > select * from Series se LEFT OUTER JOIN Precure pr ON se.series_id=pr.series_id;
Enter password:
+-----------+-----------+------------+-----------+----------------+
| series_id | name      | precure_id | series_id | name           |
+-----------+-----------+------------+-----------+----------------+
|         1 | hutariha  |          1 |         1 | black          |
|         1 | hutariha  |          2 |         1 | white          |
|         2 | max heart |          3 |         2 | shiny luminous |
|         9 | smile     |       NULL |      NULL | NULL           |
|        15 | hug       |         55 |        15 | amour          |
+-----------+-----------+------------+-----------+----------------+

この場合は、Seriesテーブルは全部出す。が、Precureテーブルに無いものはNULLになる。 RIGHTの場合は、Precureテーブルのを全部出して、Seriesテーブルに無いものはNULLになる。

mysql> select * from Series se RIGHT OUTER JOIN Precure pr ON se.series_id=pr.series_id;
Enter password:
+-----------+-----------+------------+-----------+----------------+
| series_id | name      | precure_id | series_id | name           |
+-----------+-----------+------------+-----------+----------------+
|         1 | hutariha  |          1 |         1 | black          |
|         1 | hutariha  |          2 |         1 | white          |
|         2 | max heart |          3 |         2 | shiny luminous |
|        15 | hug       |         55 |        15 | amour          |
|      NULL | NULL      |        102 |        13 | mofurun        |
+-----------+-----------+------------+-----------+----------------+

次に考えたい

  • プリキュアのテーブル構成はより正しくはこうなるはず
    一人のプリキュアが複数シリーズに参加している。
    (といってもMHと5GoGoだけなんだけど )
    なので、シリーズテーブルとプリキュアテーブルの他に、それらを関連付けるためのシリーズ-プリキュア-関係テーブルが必要。
    っていう場合にどう書けばよいか。
シリーズテーブル
+-----------+-----------+
| series_id | name      |
+-----------+-----------+
|         1 | hutariha  |
|         2 | max heart |
|         9 | smile     |
|        15 | hug       |
+-----------+-----------+

プリキュアテーブル
+------------+----------------+
| precure_id | name           |
+------------+----------------+
|          1 | black          |
|          2 | white          |
|          3 | shiny luminous |
|         55 | amour          |
|        102 | mofurun        |
+------------+----------------+

シリーズ-プリキュア-関係テーブル
+------------+-----------+
| series_id | precure_id |
+------------+-----------+
|          1 |         1 |
|          1 |         2 | 
|          2 |         1 |
|          2 |         2 | 
|          2 |         3 | 
|          3 |         4 | 
+------------+-----------+ 

LimeChatで特定の人の発言に反応する

普段LimeChatをよく使う。slack何それ状態。

キーワードに反応する機能はあるけれど、発言内容しか引っかけてくれない。
ある人の発言は全てすぐに気付きたかったのだけど、「誰かが発言した」ことに反応するのが設定で無さそうだった。
ので、LimeChatスクリプト機能で書けた。
スクリプト機能についてはLimeChatのヘルプで分かりやすい。 ちなみに普段はバルーンはOFFにしている。

 function event::onChannelText(prefix, channel, text)
 {
  // チャンネルオブジェクトを取得
  var c = findChannel(channel);
 
   // 特定ユーザの発言に反応する
  if ( prefix.nick == '名前') {
    // キーワードに反応した状態にする
    c.highlighted = true;
    // ウィンドウをピコピコさせる
    window.blinkTitle();
    // バルーン出すなら
    showBalloon(channel, 'メッセージ');
  }
 }

追記
なんでこれが必要だったかというと、チームメンバ(2人)がチームのチャットで発言したのをすぐに捕捉したかったから。
明確に呼びかけの意思があるときは
これ確認お願いしますー >名前
っていう風に発言するので元々のキーワード機能で事足りた。
だけど、
なんかエラーメール飛んでますね
みたいなのにもすぐに反応していきたかったから。

蓬莱山ソロ

蓬莱山に登ってきました。ソロで。

経路

  • JR志賀駅
  • キタダカ道
  • クロトノハゲ
  • 打見山
  • 蓬莱山
  • ロープウェイで下山

キタダカ道

ジグザグジグザグ、ひたすらジグザグ。道中、何か所か倒木があり、くぐったり跨いだり根本の裏を回ったりした。かなり新しめの倒木もあった。 勾配はぼちぼち。昨日の雨のせいか地面は湿った落ち葉とゴロゴロした石で歩きにくかった。 f:id:yukinea:20180503225806j:plain

クロトノハゲ

クロトノハゲのちょい手前くらいから霧がかってきた。霧というか雲の中に入っていたんでしょうなあ。
看板、文字を掘って白く塗ってるんだけど、ハの字を彫りミスってる。 f:id:yukinea:20180503225817j:plain

打見山、蓬莱山

クロトノハゲを過ぎて少しすると、ロープウェイの音や、子供の騒ぐ声が聞こえてきた。
打見山上はアクティビティでにぎわっていた。ターザンみたいなやつとか。霧で全然見えないのに。
打見山からさらに少し歩くと蓬莱山山頂に着く。打見山からゲレンデを登っていくのだが、霧のせいで向かってる先が合ってるのかどうかさっぱり分からなかった。 蓬莱山山頂からの景色は案の定、眺望ゼロ。晴れていたら琵琶湖を眺めることができただろうに。まことに残念である。

ロープウェイ

予定より時間がかかったのと、あの道を下山するのつらいなってなったのでロープウェイで降りることにした。
大人片道1400円。高ぇ。。でも歩いて下山すると日が沈む、というか山の東側を降りるので暗くなるのが早いので危ないのとやっぱりあの道戻るのつらいので1400円の価値はある。
ロープウェイ乗って少し降りると、めっちゃ晴れてた。やっぱり山のてっぺんだけ雲の中だった。めっちゃ寒かったもん。
んでロープウェイの中から見た景色めっちゃいいのよね。悔しいのう。 f:id:yukinea:20180503225825j:plain

Test::mysqld で copy_data_fromの機能を使う

雑なブログの方が毎日続いていて偉い。

最近はテストを書こうとしていて、そんな中でTest::mysqldを使ってみて躓いたところを書く。

そもそもTest::mysqld

めっちゃ便利感あって、DBに入れたり消したり更新したりするような処理をテストするときって、テストで使ったデータのお掃除が必要になる。
でもこいつを使ってやると、テストのたびに新しくmysqldを立ち上げ、その中でDBの処理を行い、終わったら綺麗に消えてくれる。

ただし、新しく立ち上がるmysqlは初期状態なので、テストするDBと同じようにテーブル作ったりデータを流し込むSQLを流し込んでやらないといけない。
が、copy_data_from というメソッドがあり、これを使うと、実際のデータから同じ環境を一発で用意してくれる。すごい。

この辺とか見てたわけです。 Test::mysqld 0.17 でテストがもっと簡単になる話 - kazuhoのメモ置き場

今回の要件

MyApp/DB.pmという自作モジュールがあり、DBへの接続とかユーティリティ的メソッドを持っている。こいつをテストしたい。

とりあえずテストコード

留意点はコード中にコメントで書いた

こいつをテストしたい
package MyApp::DB

# $ENV{TEST_DSN}があれば見るようにする。
# こうすることで、テストやデバッグの際に任意のDBへ接続させられる
my $schema;
if ( $ENV{TEST_DSN} ) {
    $schema = MyApp::DB::DBIC::Schema->connect( $ENV{TEST_DSN});
}
else {
    my @dsn = ('dbi:mysql:mydatabase', 'USER', 'PASS');
    $schema = MyApp::DB::DBIC::Schema->connect(@dsn);
}
test.t

use Test::More;
use Test::mysqld;
use DBI;
use MyApp::DB;

$ENV{DBI_PASS} = 'hogehoge'; # 読み込ませるDBに設定されているパスワードを書く

# test 用 mysqld 起動
my $mysqld = Test::mysqld->new(
    my_cnf => {
        'skip-networking' => '',
    },
    base_dir       => 'tmp/test_mysql',
    copy_data_from => '/hoge/foo/mysql',     # 実際のDBのディレクトリをコピーしてきたものでOK。ただし、mysql.sockファイルは削除しておく。
) or plan skip_all => $Test::mysqld::errstr;


# test 用 mysqld 用の dsn を環境変数に設定する
$ENV{TEST_DSN} = $mysqld->dsn(dbname => 'mydatabase');
#  こうしてMyApp::DBをnewすると、MyApp::DBに書いた通り$ENV{TEST_DSN} のdsnにつないでくれる。
my $db = MyApp::DB->new();

print $db->mymethod();

上記で、$dbからMyApp::DBのメソッドをテストしてやると、テスト用mysqlに対して読み書きしてくれる。

躓いたところとコメント

  • コピーしてきたデータの不備
    実際のDBのディレクトリをコピーしてきたものを使おうとすると、「そのようなファイルはありません」的なエラーが出ていた。
    動いているDBのディレクトリからコピッてきたため、mysql.sockファイルがあり、これのせいだった。
    コピーしてきた中のmysql.sockは削除しておく。
  • 環境変数には文字列しかダメ
    元々はdsnには上記の@dsnのように配列の形式でやるようにしていた。
    なので、$ENV{TEST_DSN} に元々と同じように配列を入れてやったり、配列のリファレンスを渡してデリファレンスして使ったりしようとしたがダメだった。
    どうやら、$ENV{hoge}には単純に文字列しか入れられないようだ。ほんまかいな。違ってたら教えてください。
    で、実際のところ、dsnには文字列の形で指定することもでき、
    $mysqld->dsn(dbname => 'mydatabase'); をprintしてみると分かるが、文字列の形で返されている。
    なので問題なし。
  • パスワード
    Test::mysqldはnewするとDBに接続する動作がある。
    初期状態だったら問題ないが、ccopy_data_from によって実際のDBの状態を作ってくれるため、 実際のDBのパスワードが無いと接続に失敗してこける。
    そういう場合のため、$ENV{DBI_PASS} に実際のDBのパスワードを入れておいてあげると、 DBIの動作の中でこいつを使ってくれる。

ちなみに、パスワード直書きが嫌だったので、とりあえずパスワードらしく入力させる方法はこれ。

system("stty -echo");    # 入力を表示しなくなる
print "Enter DB password: ";
my $passwd = <STDIN>;      # 標準入力させる
system("stty echo");      #入力表示を戻す
chomp( $passwd );

なにわPerl でコードレビューされてきた

aenikuy.hatenablog.com

終わった後の飲み会懇親会でいい話を聞いたうちの一つで、雑なブログを作った。
いい意味で雑。雑の半分は雅の半分。

なにわPerlに参加してきた

イベント参加の後はブログ書くっていうのはやっていきたい案件なので書きます。
今日はタイトルの通り、なにわPerl に参加してきました。
今回はコードレビューをする回ってことで、事前に告知されてるお題を解くプログラムを、事前 or 当日に書き、 それを著名なPerlモンガーにレビューしてもらうという夢のような回でした。
今回のレビュアーはpapixさんでした。
メインのレビュアーはそうだけど、少人数だったのもあって参加者お互いにあれこれ言い合ってて良かった。

お題がなかなか面白い内容で、僕は訳あって事前に書けず当日にプログラム書いたのだけど、 できれば事前にじっくり書いておきたかったです。

とりあえずかいつまんで書く

例外処理の褒められ

初手でだいぶ嬉しかったこと。
お題としては、入力値の範囲は問題内容で決まっており、その前提でプログラムを書けばOKである。 なので範囲外の入力値は考慮しなくてもOKである。
でも僕は、怖いしやらなきゃダメでしょって気持ちで、入力値を受け取った直後で値をチェックして範囲外ならdieさせてた。
ほとんど癖で、そういうチェックを書いた。

レビューされる直前になって、お題としては不要だったし書かなくてもいい処理を書くのはこの場合どうなんだろうということに思い当たり一瞬不安になった。
 ところが、僕のコード見て最初にその値チェックの行を「良い。大事。」と言ってもらえました。
問題を解くアルゴリズムがどうとかコードの可読性とかじゃなくて、そういう身に染みついてることを肯定されたのがマジで嬉しかったし、そういうところの重要性の認識が合ってたのもよかった。

Smart::Args がよさそう

上記の値チェックについて、papixさんがお勧めしていたモジュール。そういうチェックがすごい楽になるっぽい。触ってみよ。

テスタビリティ

papixさんのコードは処理の分離をしっかりやっていて、「一つのことを上手くやる」みたいなところがよかった。 で、そういう風に処理が分けられているのでテストがしやすいみたい。
テスト書きたい。

プロ

コードレビューじゃないんだけど、「プロってなんなんだ」みたいなエモーショナルなこと僕が言っちゃって、 それにpapixさんと主催者のtomchaさんが答えてくれた。
僕が理解できたことをとにかく文字にしておくと、

  • それを仕事にしてお金もらっていられているならプロ。
  • プロといっても2軍、1軍、メージャーリーガーとかはある
    1軍だとしてもホームラン王もいればヒット製造機もいれば守備めっちゃ上手いとかいろいろあるね
  • プロは早く8割(ある程度以上のライン)を満たすものを作れる
    早さってのはたしかにプロらしい。早く出せばフィードバックがもらえて改善していけるし改善していけば8割が9割にもなる。
    100%のものってのは無い。趣味の域だと99%を求めることもできるかもしれないけどそれってコストバランス度外視になっちゃってるからプロっぽくない。(ごく一部の分野しか考慮してないけど)
     早く及第点のものを作るっていうの、小林賢太郎の本にも書いてあった気がする。 あっちはたしか期日よりだいぶ前にお客さんに観せられる完成品を作り、残りの期間で完成品を更新するみたいな。

良い日だった。

columnコマンドが便利そう

columnコマンドが手軽に便利だった。

ちょっとperlでゴリゴリっと処理して画面に出力して、| tee でログファイルに書くっていうのちょくちょく使うんだけど、実は画面出力を見やすくするために、printするところで地道に整形(のようなこと)をしている。
でもそれって前読んだUNIXという考え方的には良くないなと思った。
人間にとっての見た目よりも、単純な形式で出力するだけのスクリプトにしておいて、人間にとっての見た目は

$ ./script.pl | tee hoge.log | column -t

ってするのがベストなんじゃないかと思った。

以下、覚え書き

$ cat hoge.txt
hoge1
hoge2
hoge3
hoge4
hoge5
hoge6
hoge7
hoge8
hoge9
hoge10
hoge11

こんな場合は

$ cat hoge.txt | column -c 30
hoge1   hoge5   hoge9
hoge2   hoge6   hoge10
hoge3   hoge7   hoge11
hoge4   hoge8 

-c で横幅を指定できる。
並びが縦に順番になってるのを、横に順番にしたい場合は -x をつけてこう。

$ cat hoge.txt | column -x -c 30
hoge1   hoge2   hoge3
hoge4   hoge5   hoge6
hoge7   hoge8   hoge9
hoge10  hoge11

それより便利なのがこういう場合

$ cat foo.txt
domain_name, ipaddress, comment, comment2
domain1, xxx.xxx.xxx.xxx, aaa, ii'ii
domain2, yyy.yyy.yyy.yyy, hogehoge,fooooooooooo
hoge.domain.com,     zzz.zzz.zzz.zzz, 0, 1

-s で区切り文字を指定
-t でカラム数をいい感じにしてくれる

$cat foo.txt | column -t -s ,
domain_name       ipaddress             comment    comment2
domain1           xxx.xxx.xxx.xxx       aaa        ii'ii
domain2           yyy.yyy.yyy.yyy       hogehoge  fooooooooooo
hoge.domain.com       zzz.zzz.zzz.zzz   0          1

余計なスペースがあって微妙なのでこう

cat foo.txt |column -t -s ,' '
domain_name      ipaddress        comment   comment2
domain1          xxx.xxx.xxx.xxx  aaa       ii'ii
domain2          yyy.yyy.yyy.yyy  hogehoge  fooooooooooo
hoge.domain.com  zzz.zzz.zzz.zzz  0         1

-s の指定でいい感じにできなさそうだったら前段でsedとかで区切りをいい感じに置換しておくとよさそう。