2009年12月26日土曜日

OS間の文字コード互換性(Win, Mac, Linux)

最近 Mac を使い始めたのもあって、Win と Linux 間だけでなく Mac も含めてOS間の互換性を調べていましたが、文字コードについてはそれぞれが微妙に違うものを採用していたりして、本当に面倒くさいことがわかりました。今回はこのあたりをちょっとまとめてみます。

(Mac で圧縮した zip ファイルを Ubuntu で解凍した際のファイル名の文字コード問題を修正する機会があったので、その方法をこちらのページに載せておきました)

OS文字コード改行コード
Windows基本は CP932(Shift-JIS に Microsoft の独自仕様を足したもの)、最近は UTF-8・UTF-16 も。
カーネルや .NET 内部は UTF-16 と思われる
CR+LF
Linuxシステムに依るが、主に UTF-8、EUC-JP 等LF
旧 MacMacJapanese
(Shift-JIS に Apple の独自仕様を足したもの)
CR
Mac OS Xアプリケーションは MacJapanese

カーネルはおそらく UTF-8 か UTF-16

ファイルシステムは UTF-16 NFD(Mac 独自)
LF
※Mac は詳しくないのですが、全て 10.5 (Leopard) で確認しています

このようにしてみると、一般的なものにちょっと独自仕様を足したものが目に付きますね。Mac も Win も、Shift-JIS を基本にしているのでお互いにデータのやり取りはできますが、例えば Mac で作成した標準テキストを Win に持っていくと、文字は読めますが改行コードが LF と CR+LF で異なるために改行がバグります。また、一部のお互いの独自仕様が絡む文字が含まれるとやり取りができなくなる可能性があります。

Mac に関しては非常に汚いですね。Mac OS X までは Shift-JIS を独自拡張した MacJapanese ベースに改行コードとして CR を使ってましたが、Mac OS X からは UNIX ベースになったため改行コードが LF に変わっています。さらに、OS X のシステムも MacJapanese をデフォルトのエンコーディングとしながら、内部的には UTF-8 を利用し、ファイルシステム(HFS+)に NFD 正規化方式を利用した UTF-16 と全部で3種類利用しているようです。

正規化方式の話が出ましたが、「UTF-8」等と書くとほとんどが一般的な NFC 正規化方式を意味し、NFD 方式は Mac ぐらいでしか利用されていません。NFC は濁点や半濁点、ドイツ語等に見られるウムラウトの文字を含めて一字とするのに対して、NFD は「が」→「か」+「゛」のように分離して表現します。Windows や Linux では NFD に対応しているソフトは少なく、トラブルの原因になりかねません。つまり、濁点や半濁点を含むファイル名を直接テキスト等に書き出すと NFD として書き出され、一般的な UTF エディタ等では開けなくなったり、バグったりします。なので、例えばターミナルから

% ls > dirindex.txt

として書き出すと、UTF-8 NFD(UTF-8-MAC というやつです)で書き出されてしまい、Windows の UTF 対応エディタから開いた場合エラーとなる場合があります。一方で、Mac のテキストエディットでは UTF-8-MAC の文字も UTF-8 のファイルとして読み込めば問題なく表示され、どちらかを意識することなく編集できます。逆に言うと、そこに NFD が混じっていても全く区別がつかないので、Win 等に持っていく場合は注意が必要です。(ちなみに、UTF-8-MAC のテキストを開き、NFD の部分を消して同じ文字を打ち直すとその部分だけ NFC になりました)

ただ、Mac・Linux 間はどちらも UNIX ベースということもあって、Mac のデフォルトエンコーディングの MacJapanese を UTF-8 として、かつ日本語のファイル名を利用しなければ、UTF-8 ベースの Linux ディストリビューションと文字コードに関して完全互換になりそうですね。

しかし、様々な文字コードにソフトを対応させたり文字化け解消や適宜変換をしたりと、世界中でかなりの労力が裂かれてると思うのですが、いい加減統一する方向に向かないんですかね。システムの肥大化や文字化け等のトラブル、OS間の受け渡しコストとか、統一してしまえば全部解決する気がするんだけどなぁ…。UTF は全世界の文字をほぼ全部表現できることを目指してるので、もう素直に全部 UTF-8 あたりに統一すればいいのに。

…まぁ、色々と大人の事情があるんでしょうね。

2009年12月7日月曜日

[XREA] PHP による .htpasswd の生成

今回は apache で会員用ページに認証をかける時、XREA/coreserver のコントロールパネルの .htpasswd の生成ツールと同じパスワードを出力する php スクリプトの紹介です。このスクリプトを使うことで、php のスクリプトで .htpasswd を作成できるようになります。

色々と調査してみたのですが、どうやら XREA/coreserver の .htpasswd 生成スクリプトでは標準 DES を用いているようですね。他の認証方式としては md5 等もありますが、同じ入力文字列に対して得られる出力結果(ハッシュ値)が固定なので、出力結果を比較することで同じパスワードが利用されているかどうかがばれてしまいます。一方、DES には平文に加え、salt(調味料ぐらいの意味?)というパラメータの組で一意の暗号化結果が得られるようになります。これにより、同じパスワードを暗号化しても、その都度 salt を変更すれば同じ出力結果になることを避けることができます。

標準 DES を用いた場合、平文に2文字の salt を与えます。また、出力結果の始め2文字にも salt 自身が示されます。例えば、XREA/coreserver でパスワード "a" を暗号化した場合、

aiDm98/1yAB/6

を得ました。この中の、始めの "ai" が salt に該当し、元のパスワード "a" と、salt "ai" の二つの組み合わせで暗号化すれば上記の "aiDm98/1yAB/6" が得られることになります。

php でこの標準 DES による暗号化を行うには、少なくとも XREA/coreserver 上では、$pass を平文、$salt を2字の文字列として

$r = crypt($pass, $salt);

とすれば、戻り値として暗号化結果が得られます。なお、php の crypt 関数は引数の状態(数や文字数)やオペレーティングシステムによって挙動が違うようなので、他の環境で完全に動作するかは未確認です。以上から、XREA/coreserver の出力結果を PHP からも再現したい場合、

echo crypt("a", "ai");

とすることにより、XREA/coreserver の .htpasswd 生成ツールと同じ結果が得られます。実行してみると、

aiDm98/1yAB/6

きちんと同じパスワードが得られていることが確認できました。

2009年11月5日木曜日

ssh を使って Linux のあるPCのウィンドウを自分のPCで表示

以前は xauth や xauth 等を利用して、サーバーの許可設定や環境変数 DISPLAY を注意深く設定して、やっとウィンドウを他のPCに取り出すことができました。以前は面倒くさい手順が必要でしたが、最近の Linux では ssh がトンネル化してくれて非常に簡単にできるようになっていますね。

ウィンドウを送りたい端末Aと、ウィンドウを受け取って表示したい端末をBがあるとします。まず、Aの側で ssh サーバーを起動しておきます。Ubuntu の場合、

% sudo apt-get install ssh

でインストールしてくれて、しかもサーバーの自動起動設定もしてくれます。

そしてBの側から、

% ssh -X [Aのユーザー名@AのIPアドレス]

と -X オプションを書いてAにログインすると、そのセッションで開いた X のアプリケーションはBのディスプレイに表示されます。これで、共用の Linux のパソコンがあった場合、誰かが使っていても画面を自分の画面に引き寄せて作業できるようになります。

もちろん、この方法はBの側の X サーバーを利用するので、X サーバーが起動してない Windows の端末エミュレータ等(Tera Term とか Putty)では使えないので気をつけてください。

2009年11月3日火曜日

Ubuntu 9.10 への OpenGL のインストール

Ubuntu 9.10、出ましたね。これに合わせて環境を 9.10 でクリーンインストールし直したので、その時の OpenGL 関係のインストール方法についてまとめておきます。

注意!この記事は古い情報になっています。最新の方法についてはこちらをご覧下さい(リンク先は Ubuntu 10.04 への、となっていますが 9.10 でも適用できると思います)

まず、ビデオカードのドライバを入れます。ビデオカードのベンダーのホームページに行ってドライバをダウンロードし、手順に従ってインストールします。[システム]-[システム管理]-[ハードウェア・ドライバ]あたりからでも入れれるはず。インストール後、

% glxinfo | head

で、次のような一文が見つかるとOK。

direct rendering: Yes

次に、依存関係のファイルをインストール。必要なパッケージは次の通り。

  • OpenGL … libgl1-mesa-dev
  • glut … freeglut3-dev
  • glew(最新・拡張機能を使いたい場合) … libglew1.5-dev
  • その他依存ファイル … libxmu-dev, libxi-dev

インストールは、基本的に apt で自動的にやってくれます。ターミナルを開いて、

% sudo apt-get update
% sudo apt-get install libgl1-mesa-dev freeglut3-dev libglew1.5-dev libxmu-dev libxi-dev

あたりでできるはず。これらの依存関係もインストールしてよいか尋ねられた場合は、もちろんインストール。1回目の sudo コマンドでパスワードを求められるので入力する。

ここまでできたら、こちらの4章に従ってサンプルプログラムを動かしてみましょう。有名な床井先生の入門ページです。あ、もうインストールは全て終わっているので、2章の「GLUT のインストール」は必要ないですよ。

ちなみに床井先生のページの3章のコンパイルコマンドについて、プログラムのソースコードが program.c の時、Ubuntu 9.10 では

% cc program.c -lglut -lGLU -lGL -lXmu -lXi -lXext -lX11 -lm -lpthread

で動くことを確認しています。

ファイルを一括ダウンロード&一括ファイル名編集

最近、ある会員用ページで200曲以上の mp3 データをダウンロードできる権利を手に入れました。ところが、まとめてダウンロードできずに一つ一つリンクが貼ってありました。しかも、そのダウンロードしたファイルが全てローマ字表記で、"hajime.mp3", "tsugi.mp3" といったように非常にわかりにくかった。

番号タイトルファイル名
1始めの曲hajime.mp3
2次の曲tsugi.mp3
3三番目の曲sanbanme.mp3

そこで、データを丸ごとダウンロードし、このダウンロードした mp3 をローマ字じゃなくて全部日本語の曲名のファイル名にしたい、というお話。例えば、「hajime.mp3」は「1-始めの曲.mp3」に、「tsugi.mp3」は「2-次の曲.mp3」に変換といった具合。もし数曲程度なら手動で整理した方が早いけれど、3桁もの数の曲データの手動ダウンロードと手動ファイル名修正はいかがなものかと。

とりあえず、ファイルの一括ダウンロードはすぐにできそうなので、ダウンロードツールを探しました。

あ…会員ページにログインする機能のないダウンロードツールは利用できないんだった。。そこで、Firefox のアドオンで、表示されているコンテンツを丸ごとダウンロードできる DownThemAll をインストールしてデータを一括ダウンロードしました。

ダウンロード設定。しばらく待つと、全部のファイルが無事ダウンロード完了。

さて、次はファイル名編集。幸い、HTML のテーブルの形でデータが整理されてたので、HTMLのソースから該当のテーブル部分を抜き出して、XML パーサにかけたらできそうですね。HTML の元のソースから table タグを見つけて抜き出し、拡張子 XML で保存。

ブラウザで開いてみると…構文エラーが発生。

エラー: invalid attribute value
<td width=5%>

…そうか、XML の構文はかなり厳格で、属性値にダブルクォーテーションがないとか、閉じ括弧があらゆるタグについてないとエラーになるとか、色々あったんですね。HTML 構文はそのあたり適当でもブラウザが適宜解釈してくれるんだけど、XML ではそうは行かない。HTML を XML に変換するのに躓くのは想定外というか、そういうことがあったって忘れてた。

そこで、このあたりを修正できるツールを探していると、Tidy というツールを発見。XML ドキュメントのエラーを探して自動で修正してくれるツールみたいです。早速 Windows 版をインストールして試してみる。

お…文字化けで肝心の日本語を変換できず…。あれこれ他のページを探してみるとこのページを発見!

http://html.idena.jp/program/index.shtml

このツールを使うと、無事に XML 構文が修正されて、XML として読み込むことができました。

ちなみに、Linux にても Tidy は利用できました。文字コードを UTF-8 にしておいて、次のコマンドを使いました。

% tidy -utf8 -asxml input_file > output_file

ただし、この方法で生成した出力ファイルには1つ問題がありました。&nbsp; が存在した場合、これは XML で定義されていないためにこの後使う expat でエラーになるようです。あまり綺麗ではないのですが、結局ストリーミングエディタを使って無理やり矯正してみました。

% sed -e 's/&nbsp;/ /g' output_file2 > out.xml

さて、この XML を構文解析して、ファイル修正スクリプトを組む必要がありますね。XML パーサだったらどこにでもあるだろう。なんとなく、python で作る方法を調べる。xml.parsers.expat あたりが軽量で使いやすそうなことがわかったので、このあたりを使うことにした。

…ソースを載せようと思ったけど、よく考えたら会員じゃない人は試せないわけね。雰囲気だけ載せときます。

#!/usr/bin/python
# coding: utf-8

import sys
import os
import xml.parsers.expat

# XMLファイルをパース
def parseDocument(p, file):
    f = open(file, 'r')
    p.ParseFile(f) 
    f.close()

# 要素の開始(<td> とかのときに呼ばれる。attrs には td の属性値)
def start_element(name, attrs):
    # if name == 'td': とかの処理

# 要素の終了(</td> とかのときに呼ばれる)
def end_element(name):
    # if name == 'td': とかの処理

def char_data(data):
    # data に値が入る

# パーサの作成
p = xml.parsers.expat.ParserCreate()

# ハンドラの設定
p.StartElementHandler = start_element
p.EndElementHandler = end_element
p.CharacterDataHandler = char_data

# table.xmlをパース
parseDocument(p, 'table_new.xml')

ここまで結構長かった。。

[XREA] 携帯サイトへリダイレクト

RewriteEngine を使って、携帯からのアクセスがあった場合に携帯ページへリダイレクトするコードを作ってみました。
XREA/Coreserver で動作を確認しています。

http://foo.bar/

がトップページで

http://foo.bar/mobile/

が携帯サイトだった場合、ドキュメントルートの .htaccess に以下のコードを記述。

RewriteEngine on
RewriteCond %{HTTP_USER_AGENT} (DoCoMo|Vodafone|J-PHONE|SoftBank|UP\.Browser|KDDI)
RewriteCond %{REQUEST_URI} !^/mobile/
RewriteRule ^(.*) http://foo.bar/mobile/ [L]

4行目で、携帯からのアクセスならあらゆる場合に /mobile/ に飛ぶようにしてますが/mobile/ の中にいる場合も /mobile/ に飛ぶと /mobile/ トップページに常に縛られるので、既に /mobile/ の中にいた場合だけ /mobile/ トップページに飛ぶのを除外するコードが3行目の !^/mobile/ です。
モバイルモバイル!

現在のところ上手く動いてる。ただ、ルートに置くと他の全ページに影響するんでバグがあった場合ちょっと怖いですね。

source コマンドではまった

親シェルから呼び出された子シェルで export された環境変数は、呼び出し元の親シェルでは反映されない。つまり、子シェルで環境変数を設定しても、親シェルに戻った瞬間にその環境変数の設定は失われるということ。

別のスクリプトに書かれた環境変数の export を呼び出し元にも反映したいなら、そのスクリプトを子シェルで実行するのではなく、include と同じ意味を持つ source か . を利用して呼び出し元に取り込まないといけない。これだと、子シェルを作らず、親シェル上で実行される扱いになるので、別スクリプトの export が呼び出し元にも反映されるわけです。

で、その source コマンドで嵌りました。

最近の Ubuntu は dash という sh 拡張のシェルを基本にしているらしく、以前に作ってた bash スクリプトを動かすと source コマンドが存在しないエラーが発生。

ネットで調べてみると、source と . は等価変換可能、ということだったので bash で書かれた source を全て . に変換。

でも、これが上手く動作しない。

実は、bash 用コード中に

% source prog param

なんて無理やり source で呼び出したコマンド(prog)にパラメータ(param)を指定してて、bash 上ではこれでうまく行ってたんですが、

% . prog param

ってやってみたら、param が付いてこないみたい。親シェルの引数がそのまま prog に引き継がれるよ…。

どうやら source コマンドの後ろの引数の部分の仕様は未定義らしくて、実装によってその挙動が異なるみたいです。今まで使ってた bash では、引数処理まできちんとやってくれてたけど、どこでも動くわけではなかったみたい。

結局のところ、dash ではどうしても無理そうだったので

% arg=123
% . myscript

として myscript 内で変数 arg を評価するコードに…。これを見てアセンブラの push を思い出しました。しかも、このスクリプトは共有されてて今まで通り bash から読み込まれることもあるので、arg の存在の有無を判定して、無さそうなら引数を確認…とかかなり複雑に。なんか汚い…。

…まぁ、これで動作するようになったので動かないことはなくてほっとしたけどね。

2009年10月26日月曜日

Python のリダイレクトでエラー?

普通に動いている Python のプログラムをリダイレクトしたらエラーが...

codeEncodeError: 'ascii' codec can't encode characters in position 2-9: ordinal not in range(128)

調べてみると、どうやら内部仕様からこのエラーが出てくるようですね。出力文字コードに ASCII 以外の文字が含まれる時は何かにエンコードする必要があるようです。標準出力の時のみ自動でOSの利用している文字コードにエンコードしてくれるのでエラーが出ませんでしたが、リダイレクト時には何にエンコードしたらいいのかわからずエラーが出たようです。

str = u"あ"
print str.encode(sys.getfilesystemencoding())

で解決するようです。

...どうでもいいですが Python って便利って聞いてますがですが、文字コード周りは面倒ですね...。

2009年10月20日火曜日

ブログ作成

ブログ始めました。

技術系の話題がメインです。