Unicodeなファイル名を操作するのに最適なスクリプト言語(Windows限定)

やりたいことは、フォルダの中にある全てのファイルを圧縮するとか、リネームするとか、そういう簡単な作業。こういったことを、WindowsUnicodeの(CP932の範囲にない)文字を含むファイルに対して行いたい。

少なくとも今のところは、RubyとかPerlではそういったファイルを普通に扱うことができない*1Python 3.0 では扱えるという情報も聞いて試してみたものの、以下のような結果になったりして……

>>> import os
>>> os.listdir('.')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Python30\lib\io.py", line 1491, in write
    b = encoder.encode(s)
UnicodeEncodeError: 'cp932' codec can't encode character '\u9dd7' in position 14: illegal multibyte sequence

取るのは簡単だけど、コンソールの文字コードがcp932なのでそのままでは出力できなかったりする。コマンドプロンプトの dir コマンドのようにそのまま表示したいんだけど……。ともかく、Pythonをロクに知らないということもあって私には少々扱いづらい。

ということで、やっぱりUnix文化圏由来のツールではなく、WindowsならWindowsに用意されているものでやった方が良いという結論になった。それが、JScript

たとえば、あるフォルダの中のファイル全てに ".bak" をつける場合は以下のようになる。

var fso = WScript.CreateObject("Scripting.FileSystemObject");
var shell = WScript.CreateObject("WScript.Shell");

var folder = fso.GetFolder("C:\\tmp");
var files = folder.Files;

for(var en = new Enumerator(files); !en.atEnd(); en.moveNext()) {
    var name = en.item();
    WScript.Echo("moving " + name + " ...");
    fso.MoveFile(name, name + ".bak");
}

こういうスクリプトファイルを適当にファイルに保存したら、コマンドプロンプト

C:\tmp> cscript test.js

のように cscriptコマンドで実行できる。Unicodeのファイル名でも問題なく操作できるし、WScript.Echo の出力もPythonのようにエラーにならず綺麗に表示される。おまけに最近のWindowsなら何のインストール作業も不要。

VBはよくわからないし、バッチは貧弱すぎるので基本JavaScriptJScriptが私にとっては最適だった。

*1:Perlでは Read and write Unicode filenames on Win32 Perl の方法で扱えるみたいだけど面倒…

C# をやることになった

C#.NET をやることになった。ちょっと古い C# 2.0 を使っているので 3.0 の型推論とかが無いのがちょっと残念だけど。

まだ初心者だけど、いまのところのお気に入り点

usingブロック

以下のように書くと、ブロックを抜けるときに、自動で引数に指定した変数の IDisposable#Dispose() メソッドを呼んでくれるというもの。*1

using (StreamReader reader = new StreamReader("file.txt"))
{
    // readerを使った処理...
}

上のコードはRubyのこれのようなもの。

File.open("file.txt") do |file|
  # file を使った処理...
end

delegate

Javaのように無名クラスを介さなくても、簡単に関数オブジェクトやクロージャのようなものが作れる。delegate は += で連結することもできる。

class Program
{
    delegate void TextWriterDelegate(TextWriter writer);

    static void Main(string[] args)
    {
        TextWriterDelegate testDelegate = CreateWriteLineDelegate("テスト");
        OutputToConsole(testDelegate);    // 「テスト」と表示
        testDelegate += CreateWriteLineDelegate("テスト2");
        OutputToConsole(testDelegate);    // 「テスト」「テスト2」と表示
    }

    static TextWriterDelegate CreateWriteLineDelegate(String text)
    {
        return delegate(TextWriter writer)
        {
            writer.WriteLine(text);
        };
    }

    static void OutputToConsole(TextWriterDelegate twDelegate)
    {
        twDelegate(Console.Out);
    }
}

GenericsがILレベルでサポートされている

JavaGenericsは単なるシンタックスシュガー・コンパイラのチェック機能で、コンパイル後のクラスファイルに型引数の情報が残らない。なので、たとえば

class Foo<T> {
    private Class clazz = T.class;
}

のようなことはできず、たとえばこんな場合などにちょっとダルいことになる。

C#ではILのレベルでGenericsをサポートしているので、

class Foo<T> {
    private Type type = typeof(T);
}

が可能。ほかにも値型のboxing/unboxingが発生しないなどのメリットがある。

コンパイル後のIL(中間言語)が確認しやすい

ILDasm.exe というGUIの逆アセンブラが標準でついていて、ILレベルでどういう処理をしているかわかりやすく確認できる。

逆にダメな点

Microsoft製の)標準ライブラリのソースが確認できない。これが最悪。

あとは、コレクション関係のライブラリが貧弱なこと。SortedList を逆順にすることすら こんな手順 が必要って本当なんだろうか……。

*1:Stream などを対象にした場合に Dispose() と Close() が全く同じなのかどうかはドキュメントに書いてない。ただ、確認した限りでは同じらしい。

rsyncでFAT32にバックアップ

MacBookMac OS X 10.4)のデータを外付けのHDD(Windowsと共用するのでFAT32)にバックアップしようとして

$ rsync -av $HOME/Documents /Volumes/Backup

とやったら、コピーできることはできるんだけど何故か2回目以降も全ファイルがコピーされている模様。

調べたらFAT32では更新時刻の精度が2秒なので、毎回更新時刻がずれているとみなされてしまうそうな。

$ rsync -av --modify-window=2 $HOME/Documents /Volumes/Backup

これでOKだった。

ニコニコ動画をRubyスクリプトで操作(+Firefoxとのセッション共有)

ニコニコ動画Rubyで操作する方法として、nicovideo という直球な名前の使いやすいgemがあるのを知った。使い方はとても簡単。普通に gem install nicovideo でインストールして、たとえばデイリーランキングのTSV出力であれば

require 'rubygems'
require 'nicovideo'

nico = Nicovideo.new('メールアドレス', 'パスワード')
nico.ranking.each_with_index do |vp, i|
  puts [i+1, vp.video_id, vp.title].join("\t")
end

これだけ。(ちなみに規約をみるとダウンロードは違反っぽいので自重)

ただ、これだと別ブラウザでの再ログイン扱いになるため、普通のブラウザで見る場合は再度ログインしないといけない。これは面倒なので、Firefox 3のセッションを共有してみる(ほかのブラウザでも、永続化されてる Cookie のはずなので似たようなことはできると思う)。

RubyのMechanizeのMozillaクッキー読み込み機能が重いので迂回する on Firefox3
を参考にさせていただきました。

こんな感じ。あらかじめ gem install sqlite3-ruby しておいて、

require 'rubygems'
require 'nicovideo'
require 'sqlite3'

# Macなら、こんな感じの場所に Firefox の cookie データが入っている
# 参照先記事にもある通り、自動でもとれるけど面倒なので手抜き
dbf = '/Users/ユーザ名/Library/Application Support/Firefox/Profiles/プロファイルID.default/cookies.sqlite'

# name の絞り込みは適当。user_session だけあればいい?
sql = << 'SQL'
SELECT host, isHttpOnly, path, isSecure, expiry, name, value
FROM moz_cookies
WHERE host = '.nicovideo.jp'
  AND name not like '\_\_%' escape '\'
SQL

# このへん丸パクリ(すみません)
cookie = String.new
p = Proc.new{|s| s.to_i.zero? ? 'TRUE' : 'FALSE'}
db = SQLite3::Database.new(dbf)
db.execute(sql) do |r|
  cookie << [r[0], p.call(r[1]), r[2], p.call(r[3]), r[4], r[5], r[6]].join("\t") << "\n"
end

# ログインはしないので、Nicovideo::Base を直接指定して auto_login を false に
nico = Nicovideo::Base.new(nil, nil, false)

# 内部で使っている WWW::Mechanize に cookie をセット
nico.agent.cookie_jar.load_cookiestxt(StringIO.new(cookie))

# 以下前と同じように操作

うまくいくことを確認したら、お好みで前と同じように Nicovideo.new('メールアドレス', 'パスワード') に戻せば、「有効なCookieが取れればそのセッションを使い、取れなければ指定したアカウントで自動ログイン」という動作にもできる。

Maven2 のアーティファクトバージョン指定フォーマット

Maven2 で、アーティファクトのバージョン指定をするときには、"1.0.2" のように完全にバージョンを固定するのではなく、"1.0.x" の x の部分は最新版を自動で使う、のような指定もできる。でも、そもそも公式サイトのどこに記法についてのドキュメントがあるのかよくわからない……。

ということで、一番確実なソースを見てみることにする。2.0.x/2.1.x ではどこにあるかというと、
maven-artifact/src/main/java/org/apache/maven/artifact/versioning/VersionRange.java
の createFromVersionSpec メソッドあたりのようだ。Javadocに書いてある内容は、

1.0
バージョン1.0
[1.0,2.0)
バージョン1.0以上、2.0未満
[1.0,2.0]
バージョン1.0以上、2.0以下
[1.5,)
バージョン1.5以上
(,1.0],[1.2,)
1.0以下または1.2以上

ということで、つまり概ね

  • カギ括弧は、そのバージョンを含む範囲の開始(左括弧の場合)、または終了(右括弧の場合)
  • 丸括弧は、そのバージョンを含まない範囲の開始(左括弧の場合)、または終了(右括弧の場合)
  • カンマは、バージョンの範囲を区切る

のようだ。
つまり、たとえば Wicket の 1.3.x の最新版を使いたければ

<dependency>
  <groupId>org.apache.wicket</groupId>
  <artifactId>wicket</artifactId>
  <version>[1.3,1.4)</version>
</dependency>

になりそうだ。単に [1.3,) だと、1.3.x だけでなく 1.4 なども使われてしまう。

…というのをMacNetBeansで試してみると、「No versions are present in the repository for the artifact with a range」とか言われてビルドエラー。NetBeansMaven Pluginが内蔵しているMavenだとうまくいかないようなのでプロジェクトのプロパティで「Use External Maven for Build Execution」にチェックを入れたら直った。なんだかよくわからない。

Spring Framework のサポートポリシー再改訂

前回 のさらに続き。Rod Johnson さんがTSSの掲示板で「タグづけポリシーを再検討している」と言ってましたが、その通り Spring Framework のサポートポリシーが再度改訂された模様。

http://www.springsource.com/products/enterprise/maintenancepolicy/faq に書かれた改訂内容(の一部)

1. SpringSource will make regular source and binary releases of the current major version of Spring available to the community until the next major version is available (defined as a release candidate for that version). After this point we will make all bug fixes and improvements available to the community in the current version only. Any back-ports will only be made available to subscription customers.

つまり、あるメジャーリリース(2.5 とか、小数点1個までのリリース)に対するメンテナンスリリース(2.5.6 とか)をコミュニティに提供する期間は、以下のように変更されたみたい。

  • 改訂前: メジャーリリース後3ヶ月間
  • 改訂後: 次のメジャーリリースが入手可能(=release candidate)になるまで

GA/final版ではなくrelease candidateというのは若干気にならないでもないですが、個人的には一応バランスのとれたポリシーになったと思う。よかったね。

attachment_fu プラグインの脆弱性っぽいものについて

報告して修正もしてもらえたのでメモ。


以前 穴があると言ったプラグインは attachment_fu です。READMEに書いてあるような mass assignment

@attachable_file = AttachmentMetadataModel.new(params[:attachable])

を行っている場合、プラグインやサーバの設定にもよりますが最悪 Rails が動いているサーバ上のあらゆるファイルを攻撃者に読み取られてしまいます。

とりあえず、

@attachable_file = AttachmentMetadataModel.new
@attachable_file.uploaded_data = params[:attachable][:uploaded_data]

のように uploaded_data だけに(独自の属性を追加していればそれらもですが)明示的に入力値をセットするよう変更するか、モデルに

attr_accessible :uploaded_data

をつけるようにするなど*1、不用意な mass assignment を避けるよう何らかの対策をしたほうがいいです*2。攻撃コードは書くまでもないので割愛しますが、昨日も書いた通りやっぱり mass assignment がデフォルトで有効なのは危なすぎる気がします。Railsのコミッタほどの人でも対処を忘れるような機能はデフォルト off にしたほうがいいと思うんですが……。

*1:ただし、以前のバージョンに後者の方法を適用するとサムネイル作成で例外が発生します。

*2:修正済みなので最新版に更新でも可ですが、あまりテストされてなさそうなのと、作者の方によると近々 rewrite ブランチのほうにスイッチするそうなので、タイミングとしては微妙かも。