09 September 2009

コードを書いていて楽しいとき、楽しくないとき

上記のような作業の自動化スクリプトは、だいたいAPIを呼ぶだけみたいなコードになるので、コードを各作業そのものはあまり楽しくない。ただし、できあがったものは便利だし、ある程度複雑な処理をいっぺんにしてくれるとうれしい。同様にちょっとしたwebプログラミングも、だいたいDBにアクセスして、そのデータをどう表示するかという作業なので、書くこと自体はそんなに楽しくない。ただこちらも、ある程度複雑なものになると達成感がある。

アルゴリズムがある程度複雑なコードは書いていて楽しい。トップコーダーの難し目の問題とか、研究で書くようなコードがこれに当てはまる。人間の脳ではデータの変化を追いきれない程度に複雑なアルゴリズムなので、シミュレーション的な面白さというか、ある意味ブラックボックスの中にデータを入れたらこういう出力になった不思議だなー、と感じるのが、面白く感じる原因だと思う。そういえばwinnyの金子さんもそんなこと言ってたような。これかな。

あとは、ライブラリなど、コードを再利用できる形にまとめて、他の人でも使えるようにするのは楽しい。gsocもそうだし、去年研究で書いた、ある特殊なカメラの画像をopencvで処理するためのラッパークラスを書いたり、コードだけじゃなくてそのドキュメントを書いたりするのは楽しかった。今年の4年生がそれを使ってくれているのもうれしい。ライブラリを設計する面白さと、後々にまで有用(と思われる)ものを作ることが、楽しく感じる原因になっていると思う。

結論はとくにない。

ftp経由でサーバにアクセスしてファイルを編集する書き捨てperlスクリプト

16個の別々のサーバにある、ほぼ同内容のファイルを編集したい。編集内容は全部同じ。サーバにはftpでしかアクセスできない。という状況にバイト中なりました。

手作業でやるのは当然面倒だったんで、perlでやることに。処理の内容は、16個のサーバそれぞれに対して、ftpからファイルをDL、内容書き換え、アップロードして上書きという、手作業をそのままコードに書き下しただけの単純なもの。サーバの情報(ホスト名、パスワードや対象ファイルの位置)は、タブ区切り(tsv)形式の別ファイルにし、読み込んで使うことに。ファイルの編集内容は、ファイル内の"@charset "euc-jp";"という行を、ファイルの先頭に移動させるというもの。また、念のためダウンロードしたファイルの、編集する前、後の2つをサブディレクトリにバックアップするようにしました。

コードはこんな感じ。

#! /usr/bin/perl

use strict;
use warnings;
use Net::FTP;

my $hostFileName = shift or die "Usage: $0 ";

open my $fh, '<', $hostFileName or die "Cannot open $hostFileName $!";

while (<$fh>) {
chomp;
my ($local, $host, $id, $pass, $filePath, $file) = split /\t/, $_;
my $fullPath = $filePath . $file;

print "Connecting to $host and getting a $file ... ";    
getFromFTP($host, $id, $pass, $fullPath);
print "Done!\n";

unless (-d $local) {
system "mkdir $local";
}

my $backupName = $local . "/" . $file . ".bak";
if (-e $file) {
system "cp $file $backupName";
} else {
die "File $file doesn't exist\n";
}

print "Editing a $file ... ";    
editFile($file);
print "Done!\n";

print "Connecting to $host and uploading a $file ... ";
putIntoFTP($host, $id, $pass, $filePath, $file);
print "Done!\n";

if (-e $file) {
system "mv $file $local";
} else {
die "File $file doesn't exist\n";
}
}

close $fh;

print "Woofoo! Now all operation has finished :)\n";

sub getFromFTP {
my ($host, $id, $pass, $fileNameToGet) = @_;
if (scalar @_ != 4) {
die "Number of arguments are wrong\n";
}

my $ftp = Net::FTP->new($host) or die "Cannnot connect to $host: $@\n";
$ftp->login($id, $pass) or die "Cannot login ", $ftp->message;
$ftp->get($fileNameToGet) or die "Cannot get $fileNameToGet", $ftp->mesasge;
$ftp->quit;
}

sub putIntoFTP {
my ($host, $id, $pass, $path, $fileNameToPut) = @_;
if (scalar @_ != 5) {
die "Number of arguments are wrong\n";
}

my $ftp = Net::FTP->new($host) or die "Cannnot connect to $host: $@\n";
$ftp->login($id, $pass) or die "Cannot login ", $ftp->message;
$ftp->cwd($path) or die "Cannot change directory ", $ftp->message;
$ftp->put($fileNameToPut) or die "Cannot put $fileNameToPut", $ftp->mesasge;
$ftp->quit;
}

sub editFile {
my $file = shift or die "Number of arguments are wrong\n";

open my $fh, '<', $file or die "Cannot open $file $!";

my $output = '@charset "euc-jp";';

while (<$fh>) {
s/\@charset \"euc-jp\";//;
$output .= $_;
}
close $fh;

open my $fho, '>', $file or die "Cannot open $file $!";

print $fho $output;

close $fho;
}

サーバ情報を記述したtsvファイルはこんな感じ。nameはサブディレクトリの名前を付けるときに使います。

name ftp.example.com id password path/to/file filename

気になった点

print "Now processing ... ";
foo();
print "Done\n"

こんな風に、処理の進捗状況をprintで表示させようとしたけど、foo()のあとに両方のprint文の文章が表示されて、期待通りの動作ではなかった。たぶん、標準出力のバッファの扱い方の問題だと思う。バッファをフラッシュする方法とかあるのかな。気が向いたら調べるかも。

また、ファイル中の高々2行(先頭に1行追加、途中1行削除)するだけの処理のために、ファイル全体を操作したり、ファイルを開き直したりしていて、かっこわるい。