2010-12-15

Ubuntu でネットワーク周りが認識されなくなったときの対処

ブレーカーが落ちて Ubuntu マシンが落ちた後,ネットワークに全くつながらなくなり,メニューバーには network is disabled と表示されるようになった.また /etc/network/interfaces には lo しかなくなっていた.

これを復旧するには以下のようにする.

# service network-manager stop
# rm /var/lib/NetworkManager/NetworkManager.state
# reboot

2010-09-07

MacPortsのPortfile開発

構成

  • オレオレ MacPorts リポジトリ www.example.com:/srv/www/macports (Linux サーバ)
  • ローカル開発マシン (Mac)
    • 開発中パッケージ置き場 : ~/proj/macports/work
    • オレオレリポジトリのミラー : ~/proj/macports/mirror

準備

決めること

  • パッケージの名前 : example-app
  • バージョン : 0.1.0
  • パッケージのカテゴリ : sysutils

リポジトリミラーの準備

$ cd ~/proj/macports/mirror
$ rsync -az --delete www.example.com:/srv/www/macports/ .

パッケージの準備

$ cd ~/proj/macports/work
$ git clone http://www.upstream.com/example-app.git
$ cd example-app
$ git checkout -b macports
$ mkdir macports
$ vi macports/Portfile
# vim: set ft=tcl et sw=4 ts=4 sts=4 :
PortSystem          1.0
name                example-app
version             0.1.0
categories          sysutils
maintainers         metalglue@example.com
description         example-app
long_description    example-app
master_sites        http://www.example.com/macports/distfiles/
$ git add macports
$ git commit

最初のアップロード

tar アーカイブファイルに固めて,チェックサムを計算し,Portfile に記述する.

$ cd ~/proj/macports/work
$ tar zcf ~/proj/macports/mirror/distfiles/example-app-0.1.0.tar.gz example-app
$ md5sum ~/proj/macports/mirror/distfiles/example-app-0.1.0.tar.gz
236aaec0cd680a4d254d26fd6820043d
$ vi example-app/macports/Portfile
checksums md5 236aaec0cd680a4d254d26fd6820043d

それらをミラーディレクトリにコピーして,portindex コマンドでインデックスを作り,アップロードする.

$ mkdir ~/proj/macports/mirror/portfiles/sysutils/example-app
$ cp example-app/macports/Portfile ~/proj/macports/mirror/portfiles/sysutils/example-app
$ cd ~/proj/macports/mirror/portfiles
$ portindex
$ cd ..
$ rsync -av --delete ./ www.example.com:/srv/www/macports

テスト

$ sudo port sync
$ port clean --all example-app
$ port -v install example-app

2010-07-24

Vim で読み込んだファイルをリストアップする方法

Vim で,読み込んだファイルをリストアップする方法をよく忘れるのでメモしておく.

:scriptnames

また,自動的に読まれるスクリプトを置くディレクトリのリストは以下のようにして見ることができる.

:set runtimepath

2010-07-10

MacPorts でどのバリアントでインストールしたかを調べる方法

MacPorts でインストールしたときにどの variants を指定したかを調べるには installed コマンドを用いる.

$ port installed
git-core @1.7.1.1_0+bash_completion+doc+gitweb+svn (active)

2010-07-03

関数とメソッドの違い

オブジェクト指向言語におけるメソッドを関数で実装できるだろうか?

最近のオブジェクト指向言語では,関数型言語の特徴であるファーストクラスオブジェクトとしての関数が提供されていることが多い.このような言語の場合、メソッドは関数なのだろうか?インスタンス変数に関数を代入すればそれが即ちメソッドなのだろうか?しかしことはそう単 純ではない.レシーバ (C++, Java, JavaScript における this) の扱いをどうするかという問題があるからである.

JavaScript

JavaScript ではまさにメソッドが関数として実装されている.

o = {
    data: "hello",
    method: function () { alert(this.data); }
};
o.method();

とすれば "hello" が表示される.それでは以下のように一旦関数を単離するとどうなるだろうか?関数がファーストクラスオブジェクトであるからには単離して持ち運ぶことが可能でなければならない.

m = o.method;
m()

この場合 undefined が表示される.いったい method 中の this は何を指しているのであろうか?引き続いて

data = "world";
m()

を実行してみれば分かる通り ("world" が表示される),この場合の this はグローバルオブジェクト (window) を指している.次に全然別のオブジェクトに持って行ってみよう.

oo = {
    data: "!",
    method: o.method
};
oo.method();

今度は "!" が表示される.つまり JavaScript では呼び出し方に応じて this が指すものを変えることでメソッドを関数として実装することを可能にしていると言える.

JavaScript の仕様書では,object.variable は単純な値へと評価されるのではなく,(object の値, "variable") という組へと評価されると説明されている (8.7 The Reference Type). この組に対して関数呼び出しが適用される (object.variable()) と,this が「object の値」を指すように設定されて,object["variable"] というプロパティ値の関数が呼び出されることになる (11.2.3 Function Call). この仕様を見れば上記の挙動も理解できるだろう(「object の値」が無いときには this はグローバルオブジェクトを指す,という記述が仕様書にある).

ちなみにこの Reference Type はカッコ式を素通りするので,

(o.method)()

のときも thiso を指し,"hello" が表示される.ただしコンマ演算子が適用されるとプロパティ値へと変換されるので,

(1, o.method)()

のときの this はグローバルオブジェクトを指し,"world" が表示される.かなり高度な JavaScript パズル問題といえるだろう.

Python

Python では,メソッドのレシーバをプログラマが明示的に宣言しなければならない点で JavaScript よりもさらに単純にメソッド=関数となっているように見える.

class Foo(object):
    def __init__(self):
        self.data = "hello"
    def method(self):
        print self.data
foo = Foo()
foo.method()

とすれば当然 "hello" が表示されるが,以下はどうだろうか?

m = foo.method
m()

class Bar(object):
    def __init__(self):
        self.data = "world"
    method = m
bar = Bar()
bar.method()

いずれも "hello" が表示される.Python では JavaScript のように () による関数呼び出しの時点でレシーバが決まるのではなく,インスタンス変数の内容を取得する時点でレシーバが決定されるようになっている.

以下ではわかりやすいように,メソッド関数をグローバルで定義して実験してみる.

def m(self):
    print self.data

class Foo(object):
    def __init__(self):
        self.data = "hello"
    method = m

class Bar(object):
    def __init__(self):
        self.data = "world"
    method = m

foo = Foo()
bar = Bar()
print foo.method == m
print bar.method == m
print foo.method == bar.method

この場合いずれも False が表示される.このように,同一の関数をインスタンス変数に設定したにもかかわらず,インスタンス変数の内容を取得して比較すると異なっていることが分かる.

実は Python ではインスタンス変数へのアクセスの際に,それがユーザ定義関数であれば,ユーザ定義メソッドという別のオブジェクトが返される (Python リファレンス「3.2 標準型の階層」の「ユーザ定義メソッド」).このオブジェクト (o とする) には o.im_func に元の関数が,o.im_self にレシーバがセットされ,メソッド呼び出し時の関数とレシーバとして用いられる.

Python のメソッド=関数かつレシーバ引数の明示という仕様はシンプルできれいかもしれないが,そのしわよせは別の場所,しかも仕様書のどこにあるかもよくわからないような所に隠されている.

まとめ

関数でメソッドを実現することは一見単純なことのように思えるが,実際はそうでもないということがわかる.

Perl や Ruby でどうなっているかを調べてみるのもおもしろいだろう.

2010-05-07

ターミナルでのファンクションキーのコード

ターミナルでファンクションキーなどの特殊キーを押したときにどのような文字が入力されるかは端末ハードウェアの種類に依存していた.現在では端末はほとんどソフトウェアなので何でも出力できるが,基本的には VT 端末に従うのが通例のようだ.

iSeries Information Center

2010-04-22

VisualStudio 2008 で「最も従事する開発作業の種類」をリセットする方法

VisualStudio 2008 の最初の起動画面で「最も従事する開発作業の種類」をリセットする方法が表示されたのだが忘れそうなのでメモ.

「いつでも設定の別のコレクションを使用できるように変更できます.[ツール] メニューから [設定のインポートとエクスポート] を表示して,[すべての設定をリセット] を選択します」

2010-04-12

MacPorts 用のオレオレリポジトリの運用例

ホスト www.exapmle.com の /srv/www/example/macports (http://www.example.com/macports/, rsync://www.example.com/macports/) にセットアップすると仮定する.ディストリビューションは debian を用いる.まずディレクトリを準備する.

deb$ mkdir /srv/www/example/macports
deb$ cd /srv/www/example/macports
deb$ mkdir portfiles distfiles

/srv/www/example/macports/portfiles を rsync プロトコルでアクセスできるようにする.

deb# vi /etc/default/rsync
RSYNC_ENABLE=inetd
deb# vi /etc/inetd.conf
rsync  stream tcp  nowait  root  /usr/bin/rsync rsyncd --daemon
deb# cp /usr/share/doc/rsync/examples/rsyncd.conf /etc
deb# vi /etc/rsyncd.conf
[macports]
    path = /srv/www/example/macports
deb# /etc/init.d/openbsd-inetd restart
deb# /etc/init.d/rsync restart

Portfile を更新する.

osx$ rsync -avzh www.example.com:/srv/www/example/macports .
osx$ cd macports/portfiles
osx$ mkdir -p sysutils/samplepackage
osx$ cp somewhere/Portfile sysutils/samplepackage
osx$ portindex
osx$ rsync -avzh ./ www.example.com:/srv/www/example/macports/portfiles

パッケージ本体を更新する.

osx$ rsync samplepackage-1.2.3.tar.gz www.example.com:/srv/www/example/macports/distfiles

ports クライアントを設定する.

osx# vi /opt/local/etc/macports/sources.conf
rsync://www.example.com/macports/portfiles/
rsync://rsync.macports.org/release/ports/ [default]
osx# port -v sync
osx# port install samplepackage

Portfile の準備のしかた

osx$ rsync samplepackage-1.2.3.tar.gz www.example.com:/srv/www/example/macports/distfiles
osx$ mkdir temp; cd temp
osx$ vi Portfile
...
master_sites        http://www.example.com/macports/distfiles/
...
osx$ port -d checksum
...
checksums           md5     3b55d5016728cfb4e76b0af952b73fff \
                    sha1    51c9e64d720732a016f39475f00ca939c6bfd512 \
                    rmd160  cd74cc78de6adedb3278aa435b374695c70d92d9
...
osx$ vi Portfile
( paste above checksums )
osx$ port -d build

2010-04-09

git-buildpackage を用いない Debian パッケージの git による管理

パッケージの上流を開発しているのが自分自身の場合には,git-buildpackage のやり方を援用して自分でやるのもよいような気がしてきた.

最初のパッケージリリース:

$ cd foo  # package top directory managed by GIT
$ git checkout master
$ git checkout -b debian
$ git checkout master
... developing version 0.0.1 ...
$ git commit
$ git tag v00.00.01
$ git checkout debian
$ git merge --squash v00.00.01
$ DEBFULLNAME='Your Name' dh_make -e yourname@example.com -p foo_0.0.1 --native
$ git add debian
$ git commit
$ vi control rules changelog  # set version to 0.0.1-1
$ git commit
$ debuild -i -us -uc -tc  # answer 'y' to warning
$ git tag debian/00.00.01-01

次のパッケージリリース:

$ git checkout debian
$ dch  # set version to 0.0.1-2
... developing version 0.0.1-2 ...
$ git commit
$ debuild -i -us -uc -tc  # answer 'y' to warning
$ git tag debian/00.00.01-02

次のバージョンリリース:

$ git checkout master
... developing version 0.0.2 ...
$ git tag version/00.00.02
$ git checkout debian
$ git merge --squash version/00.00.02
$ dch  # set version to 0.0.2-1
... developing version 0.0.2-1 ...
$ git commit
$ debuild -i -us -uc -tc  # answer 'y' to warning
$ git tag debian/00.00.02-01

git-buildpackage を用いた Debian パッケージのソースコード管理

はじめに

Debian パッケージを作成するということは,要するに,上流ソースのディレクトリ内に debian/ ディレクトリを作り,その中にいろいろなファイルを用意することである.このようなディレクトリ構成のせいで,debian/ 以下のコードのバージョン管理をどのようにすればよいかが直感的にわかりにくい.

このため,debian/ 以下のコードのバージョン管理を git で簡単に行うためのパッケージ git-buildpackage が用意されている.git-buildpackage では2つのブランチ,すなわち upstream ブランチ(デフォルトでは upstream)と debian ブランチ(デフォルトでは master)を用意し,以下のような手順で管理する.

  1. 上流の新しいバージョンをインポートする.これは upstream にコミットされた後,master にマージされる.
  2. パッケージ管理者は master で作業し,debian/ 以下を作成・変更し,コミットする.
  3. 手元でうまくいったらリリースする.リリースしたポイントを記録するためにタグが付けられる.
  4. debian/ 以下にバグが発見されたら 2 に戻る.
  5. 上流がバージョンアップしたら 1 に戻る.

今回は既に Debian リポジトリにあるパッケージを git-import-dsc でインポートする方法には触れず,最初から git-buildpackage を用いるとどうなるかを説明する.

準備

例としてパッケージ foo を管理することとする.foo の上流は git でバージョン管理されていて,それは ssh://www.example.com/srv/git/foo.git にあるとする.

$ cd /tmp
$ git clone -b release0.0.1 ssh://www.example.com/srv/git/foo
$ cd foo
$ git archive --prefix=foo/ HEAD | gzip > ../foo-0.0.1.tar.gz

最初のインポート

git-import-orig コマンドを用いる.

$ mkdir -p ~/debian-foo/foo
$ cd ~/debian-foo/foo
$ git init
$ git-import-orig /tmp/foo-0.0.1.tar.gz
Upstream version is 0.0.1
Warning: Can't symlink orig tarball due to missing debian/changelog
Initial import of '/tmp/foo-0.0.1.tar.gz' ...
Succesfully imported version 0.0.1 of /tmp/foo-0.0.1.tar.gz
$ git branch
* master
  upstream
$ git tag
upstream/0.0.1

debian ブランチである master で debian/ ディレクトリを作成する.

$ DEBFULLNAME='Your Name' dh_make -e yourname@example.com -p foo_0.0.1 -f /tmp/foo-0.0.1.tar.gz
$ git add debian
$ git commit
$ vi debian/control
$ vi debian/rules
$ vi debian/changelog
$ debuild -i -us -uc -tc  # test build

うまくいったら git-buildpackage を用いてパッケージを生成する.ここで dh_make が作った .orig. ファイルを消しておかないと後で変なことになるので注意.

$ git commit
$ rm ../foo_0.0.1.orig.tar.gz
$ git-buildpackage --git-tag -us -uc -tc

下流のバージョンアップ

debian/ 以下にバグが見つかった場合.

$ vi debian/...
$ git commit
$ DEBFULLNAME='Your Name' DEBEMAIL='yourname@example.com' git-dch --release
$ git commit
$ git-buildpackage --git-tag -us -uc -tc

上流のバージョンアップ

git-import-orig でインポートしたら,dch でバージョンを 0.0.2 に上げておかなければならない点に注意.あいかわらず orig ファイルは消しておいた方が無難.

$ cd /tmp/foo
$ git pull
$ git archive --prefix=foo/ HEAD | gzip > ../foo-0.0.2.tar.gz
$ cd ~/debian-foo/foo
$ git-import-orig /tmp/foo-0.0.2.tar.gz
$ rm ../foo_0.0.2.orig.tar.gz
$ dch -i
$ git commit
$ git-buildpackage --git-tag -us -uc -tc

2010-04-08

オレオレ Debian アーカイブの作成 (mini-dinstall 編)

最終ゴール

オレオレ apt リポジトリにオレオレ deb パッケージを置いて,特定少人数に対して配布する.

今回のお題

mini-dinstall コマンドと,dupload パッケージを用いてオレオレ apt アーカイブを作成する方法を紹介する.

準備

howto: uploading to people.d.o using dputを参考に作成する.以下を仮定する.

  • ホスト名: www.example.com
  • 管理者アカウント: foo
  • URL: http://www.example.com/debian
  • ローカルパス: /srv/www/example/debian
  • パッケージアーキテクチャ: i386

mini-dinstall をインストールする.

$ sudo aptitude install mini-dinstall

アーカイブ用の公開鍵暗号鍵の生成

$ ssh foo@www.example.com
$ gpg --gen-key
Please select what kind of key you want: DSA and Elgamal
What keysize do you want? (2048)
Key is valid for? (0)
Real name: foo
Email address:
Comment: Debian Archive Key

パスフレーズは空にしておく.空にしていないと後で dupload でアップロードしたときにパスフレーズをリモートからでは入力できなくて失敗する.

この鍵の ID を取得しておく.

$ gpg -k
pub   1024D/6789ABCD 2010-04-07
uid                  foo (Debian Archive Key)

ここでは 6789ABCD が ID である.

アーカイブツリーの作成

Origin (アーカイブの作成者/管理者) が foo で Label (アーカイブの名称) が foo のアーカイブツリーを作成する.Origin, Label については Debian Repository HOWTO を参照.

$ cd /srv/www/example/debian
$ mkdir -p mini-dinstall/incoming
$ vi sign_release.sh
#!/bin/sh
rm -f Release.gpg
gpg -u 6789ABCD -bao Release.gpg "$1"
$ chmod 750 sign_release.sh

mini-dinstall の設定ファイルを作成する.

$ vi ~/.mini-dinstall.conf
[DEFAULT]
mail_to = foo
architectures = all, i386
archive_style = flat
archivedir = /srv/www/example/debian
generate_release = 1
release_origin = foo
release_label = foo
release_description = foo
release_signscript = /srv/www/example/debian/sign_release.sh

[unstable]
release_suite = unstable

アップロードの設定

手元のマシンでアップロードの設定を行う.

$ sudo aptitude install dupload
$ vi ~/.dupload.conf
package config;

$cfg{'foo'} = {
  fqdn => "www.example.com",
  method => "scpb",
  incoming => "/srv/www/example/debian/pool/main",
  # The dinstall on ftp-master sends emails itself
  dinstall_runs => 1,
};

$cfg{'foo'}{postupload}{'changes'} = "ssh foo@www.example.com mini-dinstall -b";

アップロード

*.changes ファイルがあるディレクトリで下のようにする.

$ dupload --to foo .

apt

インストールするマシンの apt を設定する.

$ sudo vi /etc/apt/sources.list
deb http://www.example.com/debian/ unstable/
deb-src http://www.example.com/debian/ unstable/

リポジトリの公開鍵をインストールするマシンの鍵束に加える.

$ ssh foo@www.example.com gpg --export -a 6789ABCD | sudo apt-key add -

これで通常の操作をすればよい.

$ sudo aptitude update

まとめ

見てきたように apt-ftparchive を使うより著しく簡単である.また,バージョンアップしたパッケージのアップロード時の問題も解決された.

ただ,apt-ftparchive を使う方法で可能だった component の利用ができないのが少し残念(sources.list の記述を deb http://www.example.com/debian/ unstable main のようにできないということ).

あと,debian ディレクトリの .htaccess を適切に設定してやる必要があるかも.

2010-04-01

MacPorts の portfile 開発のためのリファレンス

総合/ポータル

詳細

2010-03-30

squid で rsync の proxy をサポートする

Debian の squid3 パッケージのデフォルトでは,1024 以下のポート番号との通信は特定のものだけに限られていて,rsync で使う 873 番との通信を中継することができない./etc/squid3/squid.conf の

acl SSL_ports port 443

あたりに以下のように追加すればよい。

acl SSL_ports port 873
acl Safe_ports port 873         # rsync

proxy 越しの MacPorts

基本的にはシステム環境設定の「ネットワーク→詳細→プロキシ」で設定した proxy が用いられるが,port sync は rsync を用いて行われるので rsync 用の proxy を設定する必要がある.これは環境変数 RSYNC_PROXY で設定する.まず,

$ sudo visudo
...
Defaults        env_keep += "RSYNC_PROXY"
...

として sudo 時に環境変数が継承されるようにして,

$ RSYNC_PROXY=hostname:8080 sudo port sync

のようにする(あるいはシェルの初期設定ファイルに export RSYNC_PROXY=hostname:8080 を設定する).

 

もう一つの問題は,ミラーサイトからのソースのダウンロードが止まってしまうことがままあることである.例えば subversion のインストール中に cyrus-sasl2 のダウンロードで止まってしまったとしよう.このときには Ctrl-C を押して一度止めてから,以下のようにする.

$ MASTER_SITE_LOCAL=http://distfiles.macports.org/cyrus-sasl2/ sudo port install subversion

もちろん,事前に sudo を設定しておく必要がある.

$ sudo visudo
...
Defaults        env_keep += "MASTER_SITE_LOCAL"
...

2010-03-25

オレオレ Debian アーカイブの作成

最終ゴール

オレオレ apt リポジトリにオレオレ deb パッケージを置いて,特定少人数に対して配布する.

今回のお題

標準インストールされている apt-ftparchive コマンドと,dupload パッケージを用いてオレオレ apt アーカイブを作成する方法を紹介する.

準備

Chapter 2. Debian Package Management を参考に作成する.以下を仮定する.

  • ホスト名: www.example.com
  • 管理者アカウント: foo
  • URL: http://www.example.com/debian
  • ローカルパス: /srv/www/example/debian
  • パッケージアーキテクチャ: i386

アーカイブ用の公開鍵暗号鍵の生成

$ ssh foo@www.example.com
$ gpg --gen-key
Please select what kind of key you want: DSA and Elgamal
What keysize do you want? (2048)
Key is valid for? (0)
Real name: foo
Email address:
Comment: Debian Archive Key

パスフレーズは空にしておく.空にしていないと後で dupload でアップロードしたときにパスフレーズをリモートからでは入力できなくて失敗する.

この鍵の ID を取得しておく.

$ gpg -k
pub   1024D/6789ABCD 2010-04-07
uid                  foo (Debian Archive Key)

ここでは 6789ABCD が ID である.

アーカイブツリーの作成

Origin (アーカイブの作成者/管理者) が foo で Label (アーカイブの名称) が foo のアーカイブツリーを作成する.Origin, Label については Debian Repository HOWTO を参照.

$ cd /srv/www/example/debian
$ mkdir -p pool/main
$ mkdir -p dists/unstable/main/binary-i386
$ mkdir -p dists/unstable/main/source
$ vi dists/unstable/main/binary-i386/Release
Archive: unstable
Version: 4.0
Component: main
Origin: foo
Label: foo
Architecture: i386
$ vi dists/unstable/main/source/Release
Archive: unstable
Version: 4.0
Component: main
Origin: foo
Label: foo
Architecture: source
$ vi aptftp.conf
APT::FTPArchive::Release {
  Origin "foo";
  Label "foo";
  Suite "unstable";
  Codename "sid";
  Architectures "i386";
  Components "main";
  Description "Private archive for foo";
};
$ vi aptgenerate.conf
Dir::ArchiveDir ".";
Dir::CacheDir ".";
TreeDefault::Directory "pool/";
TreeDefault::SrcDirectory "pool/";
Default::Packages::Extensions ".deb";
Default::Packages::Compress ". gzip bzip2";
Default::Sources::Compress ". gzip bzip2";
Default::Contents::Compress "gzip bzip2";

BinDirectory "dists/unstable/main/binary-i386" {
  Packages "dists/unstable/main/binary-i386/Packages";
  Contents "dists/unstable/Contents-i386";
  SrcPackages "dists/unstable/main/source/Sources";
};

Tree "dists/unstable" {
  Sections "main";
  Architectures "i386 source";
};

なお,元記事では

Default::Sources::Compress "gzip bzip2";

となっていたが,これでは /etc/apt/sources.list に deb-src を指定したときに apt-get update でエラーになってしまうので上のように修正した.

アップロードの設定

手元のマシンでアップロードの設定を行う.

$ sudo aptitude install dupload
$ vi ~/.dupload.conf
package config;

$cfg{'foo'} = {
  fqdn => "www.example.com",
  method => "scpb",
  incoming => "/srv/www/example/debian/pool/main",
  # The dinstall on ftp-master sends emails itself
  dinstall_runs => 1,
};

$cfg{'foo'}{postupload}{'changes'} = "
  echo 'cd /srv/www/example/debian;
        apt-ftparchive generate -c=aptftp.conf aptgenerate.conf;
        apt-ftparchive release -c=aptftp.conf dists/unstable > dists/unstable/Release;
        rm -f dists/unstable/Release.gpg;
        gpg -u 6789ABCD -bao dists/unstable/Release.gpg dists/unstable/Release' |
  ssh foo@www.example.com  2>/dev/null;
  echo 'Package archive created!'";

アップロード

*.changes ファイルがあるディレクトリで下のようにする.

$ dupload --to foo .

apt

インストールするマシンの apt を設定する.リポジトリの公開鍵をインストールするマシンの鍵束に加える.

$ ssh foo@www.example.com gpg --export -a 6789ABCD | sudo apt-key add -

これで通常の操作をすればよい.

$ sudo aptitude update

問題点

このやりかただと,パッケージをバージョンアップしてアップロードすると,前のバージョンがそのままアーカイブに残るという問題点がある.この場合でもインストール時には最新バージョンがインストールされるが,apt-cache search や apt-cache show で複数のバージョンが表示されてしまい気持ち悪い.以下の記事を参照して新しい方法を試してみる方がいいかもしれない.

2010-03-11

X で caps lock キーを control キーにする

前記で無事大型モニタで X を起動することに成功したのだが,今度は caps lock キーを control キーにするのがうまくいかない.

Section "InputDevice"
    Identifier  "Generic Keyboard"
    Driver      "keyboard"
    Option      "CoreKeyboard"
    Option      "XkbRules"    "xfree86"
    Option      "XkbModel"    "pc104"
    Option      "XkbLayout"   "us"
    Option      "XkbOptions"  "ctrl:nocaps"
EndSection

ではうまくいかないのである(おそらく USB キーボードだから).「http://kmuto.jp/d/index.cgi/debian/input-hotplug-guide.htm」などによると,HAL や udev などからハードウェア情報を取っている関係上,xorg.conf からの情報は無視されるようである.

あちこち探し回った結果(上の文書にも記述がある),debian では console-setup パッケージがキーボードの設定の面倒を見てくれることが判明した.

$ sudo aptitude install console-setup
$ sudo vi /etc/default/keyboard
XKBMODEL="pc104"
XKBLAYOUT="us"
XKBVARIANT=""
XKBOPTIONS="ctrl:nocaps"

としてリブートすると,何とコンソールでも caps lock キーが control になり,X Window でも同様に設定された.なお上記ではキーボードを us 配列に設定しているが普通の人は pc106, jp に設定することになるだろう.

なお,console-setup を設定するとコンソールのフォントがしょぼい感じに変わってしまう.これを元に戻すには,

$ sudo dpkg-reconfigure console-setup

として,フォントを Fixed その他ではなく,「ブート時のをそのまま使う」に設定してやればよい.

X の nvidia ドライバでデュアルディスプレイの片方だけを回転する

Dell の巨大なモニタの設定が終わったので,それまで使っていた Dell のそれほど巨大でもないモニタを縦置きしてデュアルディスプレイ構成で使うことにした.このとき片方だけ画面を回転しなければならないが,これにもそこそこ苦労したので記録しておく.

まずはデュアルディスプレイの設定から.X ではデュアルディスプレイは昔からサポートされている.ただしこの設定では独立した複数のデスクトップができてしまう.Xinerama を用いるとデスクトップを統合し,それを複数のディスプレイで表示するようにできる(nvidia ドライバでは TwinView という方法もある).しかし統合デスクトップで片方のディスプレイの表示だけ回転というのは敷居が高そうだし,そもそも今回のようにディスプレイの大きさ が異なる場合,独立したデスクトップの方が便利そうなので,独立デスクトップの方で設定することにする.

最近では画面の回転(や解像度の変更)に XRandR という拡張を用いるようである.この機能を有効にし,xorg.conf の中で Option "Rotate" "left" などとすると,一応回転ができるのであるが,この場合ドライバのアクセラレート機能がオフになってしまうようなのである.いろいろ試行錯誤した結果,以下 のような方法におちついた.

まず xorg.conf:

Section "ServerLayout"
    Identifier     "Layout"
    Screen         0 "Screen 0" 0 0
    Screen         1 "Screen 1" LeftOf "Screen 0"
EndSection

Section "Device"
    Identifier     "Device 0"
    Driver         "nvidia"
    Screen         0
EndSection

Section "Device"
    Identifier     "Device 1"
    Driver         "nvidia"
    Option         "RandRRotation" "true"
    Screen         1
EndSection

Section "Screen"
    Identifier     "Screen 0"
    Device         "Device 0"
    DefaultDepth    24
EndSection

Section "Screen"
    Identifier     "Screen 1"
    Device         "Device 1"
    DefaultDepth    24
EndSection

上の別項目で説明したような解像度の設定などは取り去っているが,nVidia Quadro FX 380 の出力はどちらも DVI なので,自動で認識するようだ.また,デュアルリンクを有効にする nv ドライバの設定も nvidia ドライバでは必要ないようだ.

上のような設定で起動した後で,

$ xrandr --screen 1 -o left

とすると片方のディスプレイだけ回転できた.これを ~/.xsession などに書いておけばよい.

man xrandr を見て設定していると,つい

$ xrandr --screen 1 --output default --rotate left

としたくなるが,これはうまくいかない.--rotate でなく -o (orientation) を使うのがポイントであった.

xorg の nvidia ドライバーに大苦労した話

先日Dell の大型モニタが届いた.このモニタには Mac を接続する予定なのだが,到着はもう少し先になりそうなので,とりあえず Linux マシンをつないで凌ぐことにしたのだが,設定に大苦労したというお話.

このモニタの解像度は 2560x1600 で,DVI 接続ではデュアルリンクが必要である.これはケーブルがデュアルリンク対応というだけでなく,グラフィックカード側もデュアルリンク出力をする必要があ る.Linux マシンのグラフィックカードは nVidia Quadro FX 380 というもので,仕様的には十分対応できるものである.

xorg 付属ドライバを試す

Linux マシンも届いたばかりなので,まずはいつもの通り Debian をインストールし,squeeze に upgrade した.そして X Window をインストール開始.

$ sudo aptitude install xorg twm
$ vi ~/.xsession
exec twm
$ chmod +x ~/.xsession
$ startx

... 真っ暗.何故か kill ではうまく終了させられないので,リモートログインから,

$ sudo telinit s

で終了させる.で,いつものごとく /etc/X11/xorg.conf をいじろうとしたのだが...無い!

$ sudo dpkg-reconfigure xserver-xorg

としても何も起こらない.「Xサーバ1.7→1.8、HAL から udev « Kawaji's Weblog」 などによると,最近の X はハードウェア環境について HAL や udev から情報を取得して自らを構成するようになっていて,xorg.conf は必要なくなっているそうだ.しかし xorg.conf が無いと話が始まらない./var/log/Xorg.0.log を見たところ,内部で利用している xorg.conf の内容が表示されていたので,それを元に generic な xorg.conf を作成した.

Section "ServerLayout"
    Identifier     "Builtin Default Layout"
    Screen         "Builtin Default nv Screen 0"
EndSection

Section "Device"
    Identifier     "Builtin Default nv Device 0"
    Driver         "nv"
EndSection

Section "Screen"
    Identifier     "Builtin Default nv Screen 0"
    Device         "Builtin Default nv Device 0"
    DefaultDepth    24
    SubSection     "Display"
        Depth       24
        Modes      "2560x1600"
    EndSubSection
EndSection

まず man nv をチェックしたところ,Option "AllowDualLinkModes" "boolean" というのを発見した.

Section "Device"
    Identifier     "Builtin Default nv Device 0"
    Driver         "nv"
    Option         "AllowDualLinkModes" "true"
EndSection

としてみた.その結果,表示されるにはされたが,画面が点滅を繰り返して使い物にならない.いろいろ実験した結果,vesa ドライバで 1600x1200 ならなんとか表示されることが判明した.

Section "Device"
    Identifier     "Builtin Default nv Device 0"
    Driver         "vesa"
EndSection

Section "Screen"
    Identifier     "Builtin Default nv Screen 0"
    Device         "Builtin Default nv Device 0"
    DefaultDepth    24
    SubSection     "Display"
        Depth       24
        Modes      "1600x1200"
    EndSubSection
EndSection

nVidia 製ドライバのインストール

しかしこれでは宝の持ち腐れである.そこで nVidia 謹製のドライバをインストールすることを決意.さっそくダウンロードしてインストールしてみたところ,全然だめなことが判明した.

そこで検索したところ,「404 - Not Found」というページを発見!だが,このページの通りにやったところ,m-a clean,a-i -i -t -f nvidia-kernel-source のところで,BUILD FAIL! となって失敗に終わった.

調べてみると,nvidia-kernel-source パッケージのバグ報告ページに既に報告されていた.そこには,nvidia-kernel-source を一旦消して,apt リポジトリを sid に一時的に変更してビルドするというヤレヤレな workaround が報告されていた.いろいろやったところ成功したので,手順を記しておく.

まず,話をややこしくしないために,クリーンインストールを行い,上記の Option "AllowDualLinkModes" "boolean" を xorg.conf に記入したところまでリプレイした.以下,「404 - Not Found」に従って nvidia ドライバをビルドする.

# aptitude install nvidia-kernel-common module-assistant
# aptitude install pkg-config  # 後に sid からインストールされるのを防ぐため
# m-a -i -t prepare
# m-a clean,a-i -i -t -f nvidia-kernel-source
BUILD FAIL!
# vi /etc/apt/sources.list
{squeeze を sid に書き換え}
# apt-get update  # 安全のため aptitude でなく apt-get を使った
# m-a clean,a-i -i -t -f nvidia-kernel-source
# depmod -a
# apt-get install nvidia-glx nvidia-glx-dev nvidia-xconfig nvidia-settings
# vi /etc/apt/sources.list
{sid を squeeze に書き換え}
# aptitude update
# nvidia-xconfig

で無事成功!

2010-02-28

javac が生成するバイトコードの調べ方

javac がどのようなバイトコードを吐いているかを知るにはどうすればいいだろうか?

class test {
    String f(String s, int n) { return s + n; }
}

この test.java のバイトコードを調べてみよう.

$ vi test.java
$ javac test.java
$ javap -c test
Compiled from "test.java"
class test extends java.lang.Object{
test();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

java.lang.String f(java.lang.String, int);
  Code:
   0:   new     #2; //class java/lang/StringBuilder
   3:   dup
   4:   invokespecial   #3; //Method java/lang/StringBuilder."<init>":()V
   7:   aload_1
   8:   invokevirtual   #4; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   11:  iload_2
   12:  invokevirtual   #5; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
   15:  invokevirtual   #6; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
   18:  areturn

}

まとめ

  • javap -c classname でクラスファイルの「逆アセンブル」ができる.

2010-02-21

upstream パッケージの Debian 化 (1)

最終ゴール

オレオレ apt リポジトリにオレオレ deb パッケージを置いて,特定少人数に対して配布する.

今回のお題

前回「Septième Sens: Debian パッケージを意識した upstream パッケージの作成」で作成した upstream パッケージ my-hello を Debian 化するにあたって,最小限必要な構成はどのようなものだろうか?

作業ディレクトリ

Debian パッケージの生成作業は upstream パッケージの最上位ディレクトリで行う.

$ cd my-hello
$ mkdir debian

debian ディレクトリの中の情報を利用する dpkg-xxx などのコマンドは,作業ディレクトリとして debian ディレクトリではなく upstream パッケージの最上位ディレクトリが設定されていることを前提としている.

dh による debian/rules

今回は現時点でもっともモダンな方法であると考えられる dh コマンドを使って Debian 化してみる.man dh によるともっともシンプルな debian/rules は以下のようになる:

#!/usr/bin/make -f
%:
	dh $@

debian/rules ファイルは実行可能でなければならない.

$ vi debian/rules
$ chmod +x debian/rules

ビルドを試してみる前に,やや天下りになるが,debian/compat というファイルに 7 を書き込んでおく必要がある.man debhelper によると,dh_xxx コマンドの動作モードが debian/compat ファイルの内容によって変わるからである.過去に dh_xxx を利用して作成したパッケージが dh_xxx の仕様変更によってビルドできなくなることを防ぐためにこのような仕様になっている.

$ echo 7 >| debian/compat

それでは実際にビルドを実行してみよう.ビルドのためのコマンドは Chapter 6. Building the package によると以下のようになる.

$ fakeroot debian/rules binary
dh binary
dh: cannot read debian/control: No such file or directory

失敗した.debian/control ファイルが必要だと言われている.

Debian Policy Manual - Control files and their fields を読むと,debian/control ファイルはメールヘッダのようなパラグラフ複数からなり,第1パラグラフで必須なのが Source:Maintainer:, 第2パラグラフで必須なのが Package:Architecture:Description: となっている.

Source: my-hello
Maintainer: Your Name <yourname@example.com>

Package: my-hello
Architecture: all
Description: say hello
 This program says hello.

debian/control を上記の内容で作成し,ビルドしてみる.

$ fakeroot debian/rules binary
...
   dh_installchangelogs
tail: cannot open `debian/changelog' for reading: No such file or directory
...

今度は debian/changelog が必要だと言われている.

debian/changelog ファイルは debchange コマンドで生成できる.

$ debchange --create
my-hello (0.0.1) UNRELEASED; urgency=low

  * Initial release.

 -- Your Name <yourname@example.com>  Sun, 21 Feb 2010 22:35:59 +0900

実行するとエディタが起動するので,PACKAGE と VERSION という記述を my-hello と 0.0.1 に修正する.

$ fakeroot debian/rules binary
...
   dh_auto_install
install -d /home/yourname/my-hello/debian/my-hello/usr/local/bin
install myhello /home/yourname/my-hello/debian/my-hello/usr/local/bin
...
   dh_usrlocal
rmdir: failed to remove `debian/my-hello/usr/local/bin': Directory not empty
...

dh_usrlocal が失敗している,出力を読むと dh_auto_install で make install が実行され,debian/my-hello/usr/local/bin に実行ファイルがインストールされているのが問題なのがわかる.

man dh によると,特定の dh_xxx をオーバーライドするには override_dh_xxx ターゲットを定義すればよい.

#!/usr/bin/make -f
%:
	dh $@
override_dh_auto_install:
	make install DESTDIR=debian/my-hello PREFIX=/usr

debian/rules を上のように修正し,再度ビルドする.

$ debian/rules clean
$ fakeroot debian/rules binary
…
dpkg-deb: building package `my-hello' in `../my-hello_0.0.1_all.deb'.

となり .deb が生成された.

$ sudo dpkg -i ../my-hello_0.0.1_all.deb
$ myhello
hello
$ sudo dpkg -P my-hello
$ myhello
-bash: myhello: No such file or directory

まとめ

  • 最低限必要なファイル
  • my-hello
    |-- Makefile       # DESTDIR と PREFIX
    |-- debian/
    |   |-- changelog  # debchange コマンド
    |   |-- compat     # 7
    |   |-- control    # Source:, Maintainer:, Package:, Architecture:, Description:
    |   `-- rules*     # override_dh_xxx:
    `-- myhello
    
  • ビルドコマンド
  • $ fakeroot debian/rules binary
    
  • リビルド
  • $ debian/rules clean
    $ fakeroot debian/rules binary
    

2010-02-18

Debian パッケージを意識した upstream パッケージの作成

最終ゴール

オレオレ apt リポジトリにオレオレ deb パッケージを置いて,特定少人数に対して配布する.

今回のお題

例えば以下のようなコマンドがあるとする.

#!/bin/sh
echo 'hello'

このコマンドを提供する Debian パッケージを作りたい.この Debian パッケージの upstream にあたるパッケージはどのように作成すればよいだろうか?

パッケージ名

まずパッケージ名を決める必要があるが,パッケージ名に使ってよい文字などの規則はどこにあるだろうか?「debian パッケージ名の規則 - metalglue」に書いた通り,Debian Policy Manual - Control files and their fields (5.6.1 Source) にあり,[a-z0-9][a-z0-9.+-]+ となる.大文字やアンダースコア _ が使えないことに注意.

ここではパッケージ名を my-hello とする.

バージョン

バージョン番号の付け方はどうしたらよいだろうか?「オープンソースソフトウェアの育て方 」を通じて知った「Versioning Numbering Concepts - The Apache Portable Runtime Project」 が参考になる.これによると,バージョン番号は . (ドット) で区切られた3つの数字で,MAJOR.MINOR.PATCH という構成を取る.ドットは小数点ではない.つまり例えば 3.1.9 の次のバージョンは 3.2.0 ではなく 3.1.10 である.

MAJOR と MINOR が同じなら PATCH が違っても機能が違ってはならない.PATCH が異なるバージョン間でアップグレードしてもダウングレードしてもバグでない挙動は同じでなくてはならない.平たく言うとバグフィックスによるバージョンアップである.

MAJOR が同じで MINOR が異なるバージョンにアップグレードしたときに,使えていた機能が使えなくなってはならない.一方,ダウングレードは安全に行えないかもしれない.

MAJOR が異なる場合は,アップグレードもダウングレードも安全に行えない可能性がある.

ここではバージョンを 0.0.1 とする.

ディレクトリ/ファイル構成

以下のようにする.

my-hello/
 |-- Makefile
 `-- myhello

Makefile

普通,作成したコマンドは /usr/local/bin に,ライブラリなら /usr/local/lib にインストールされるように Makefile を記述する.今回の場合はどのような Makefile が都合がいいだろうか?

Debian のパッケージ生成では,あるディレクトリを仮のルートとしてファイルをインストールし,それをアーカイブしてパッケージとする(Chapter?3.?Modifying the source - 3.1 Installation in a subdirectory を参照).よって,任意のディレクトリをルートと考えてインストールできるようになっていることがのぞましい.

また,「Debian Policy Manual」によると,debian ではコマンドは /usr/bin に,ライブラリは /usr/lib にインストールすることになっている.この,/usr を基準にするか /usr/local を基準にするかが選択可能であることが望ましい.

これらを勘案し,また ldapscripts の upstream パッケージも参考にして,DESTDIR と PREFIX という変数でこれらを制御できるようにした.

DESTDIR =
PREFIX = /usr/local

all:

install: myhello
	install -d $(DESTDIR)$(PREFIX)/bin
	install $< $(DESTDIR)$(PREFIX)/bin

.PHONY: all install

以下のようにインストールする.

$ make install DESTDIR=/home/yourname/tmp PREFIX=/usr

疑問点

  • upstream パッケージの作者が Debian 野郎の場合,自分で Debian 用のファイル(パッケージディレクトリ中の debian/ ディレクトリの中身)を用意するだろう.こういう場合,README とか changelog とかはどこにおいておくのがいいんだろうか?
  • よくわからないので,今回は README, changelog のことは考えないことにする.

2010-02-07

debian/rules の書き方の情報源

debian/rules の書き方の情報源はあちこちにあるが,どれが時代遅れでどれが最新かよくわからないのでネットを逍遙して検討してみた.

  • 原典としては「Debian Policy Manual 4.9 Main building script: debian/rules」がある.「Debian New Maintainers’ Guide」を読むと理解しやすい.
  • Debian Developer’s Reference」によると,debhelper を使うようだ.以下のような雰囲気:
  • binary-arch: install
        dh_installdocs -a NEWS
        dh_installchangelogs -a ChangeLog
        dh_strip -a
        dh_compress -a
        dh_fixperms -a
        dh_installdeb -a
        dh_shlibdeps -a
        dh_gencontrol -a
        dh_md5sums -a
        dh_builddeb -a
    
  • Debianパッケージ作成の手引き」によると,dh_* を使うと同じようなことを何度も書かなければならなくなるということで,これを解決するのが CDBS だということだ.以下のように変数や :: ターゲットでカスタマイズする雰囲気:
  • #!/usr/bin/make -f
    
    include /usr/share/cdbs/1/rules/debhelper.mk
    
    package = hello-cdbs
    
    install/hello-cdbs::
    	$(MAKE) prefix=$(CURDIR)/debian/$(package)/usr install
    
    DEB_INSTALL_DOCS_ALL := NEWS
    DEB_INSTALL_CHANGELOGS_ALL := ChangeLog
    
  • Debian JP Project - 最近の話題」によると,debhelper v7 が提供する dh コマンドを使うと多くのパッケージで debuan/rules が 3 行で書けるようになると言うことだ.
  • #!/usr/bin/make -f
    %:
        dh $@
    
  • man debhelper, man dh 以外に公式のドキュメントはないようだが,作者のブログにいくつか記事がある.

Debian リポジトリを作成する方法の情報源

HowToSetupADebianRepository - Debian Wiki が最新情報.

Gauche で trace

Debian マシンで Gauche で trace するには以下のようにする.

まず SLIB (portable scheme library) が必要である.

$ sudo aptitude install slib

プログラム中で以下のようにする.

(define (fact n)
  (if (= n 0) 1
              (* n (fact (- n 1)))))

(use slib)
(require 'trace)
(trace fact)

(fact 5)

2010-02-06

GitHub の設定を手短に

  • GitHub へのアカウント開設はすでに済んでいるとする.
  • ssh の鍵は既に生成済みとする.
  • Account Settings にて公開鍵を登録する.
  • GitHub 側のユーザ名は常に git なので,手元のローカルユーザ名からのアクセスを可能にするために ~/.ssh/config を編集する.
  • Host github.com
        User git
    
  • 設定を確認する.
  • $ ssh git@github.com
    Hi YOURNAME! You've successfully authenticated, but GitHub does not provide shell access
    

    と出力されれば OK.

  • Dashboard の Create a Repository で新しいリポジトリを作成する.ここでは例として hellos というリポジトリを作成したとする.
    • プロジェクト名への入力に .git を付加する必要は特にない.付加するとそれがそのままリポジトリ名になり,付加しないと .git が付加されたものがリポジトリ名になる.
    • 新たにファイルを作成していく場合
    • $ git clone git@github.com:YOURNAME/hellos.git
      $ cd hello
      $ vi README
      $ git add README
      $ git commit
      $ git push origin HEAD
      
    • 既に手元に git レポジトリが存在する場合.
    • $ cd proj/hellos
      $ git remote add origin git@github.com:YOURNAME/hellos.git
      $ git checkout master
      $ git push origin HEAD
      
  • ユーザ名とメールアドレスを設定(必要?)
  • $ git config user.name YOURNAME
    $ git config user.email YOURNAME@EXAMPLE.COM
    

2010-02-02

Mac OS X の Java 環境

Mac OS X 上で Java で開発する必要がでてきたので,Snow Leopard での Java について調べ始めた.順不同で箇条書きする.

  • Java 6 が搭載されている.今日現在で 1.6.0_17 である.
  • 64 bit 版と 32 bit 版があり,64 bit 版がデフォルト.これはアプリケーション>ユーティリティ>Java Preferences というアプリケーションで変更できる.
  • javac のエラーメッセージが文字化けする!
    • エラーメッセージを英語にすればよいが,LANG ではなく LC_ALL で制御する.
    • $ LC_ALL= javac ...
      
    • エラーメッセージの文字コードを変更できる変なコマンドラインオプションがある.
    • $ javac -J'-Dfile.encoding=EUC-JP' ...
      
    • これらは /etc のどこかにあるファイルでデフォルトとして設定できるといいのだが,Java にはそのような思想はないのだろう(anywhere だから?).alias で逃げるしかないか.

情報源

apt でインストールしたものの履歴

apt-get でインストールしたものの履歴,それもコマンドラインから陽に指定したパッケージの履歴が欲しいとずっと思っていた.自分がどういったパッケージを真に必要としたかがわかり,将来の環境構築の参考になるからである.

apt-get でなく,aptitude を用いれば可能である./var/log/aptitude* に aptitude 関連の履歴がとられており,その中から [INSTALL] という表現を grep すればよい.

ただし,デフォルトでは aptitude 関連のログは 6 ヶ月以前のものは消されてしまうので,全ての履歴を調べたいなら debian をインストールしたときに /etc/logrotate.d/aptitude を変更しておかなければならない.

2010-02-01

iTerm 0.10 の不具合

iTerm を 0.10 にしてから時々表示が欠けるようになった.

SourceForge.net: iTerm.app: Detail: 2888004 - Occasional ’missing’ lines によると,Preferences の Display Refereshing Rate を遅くすると顕在化しなくなるということだ.

このレートを調整するには以下のコマンドを用いればよい.

$ ruby -e 'loop do ; puts "foo" ; sleep(rand*0.01) ; end'

2010-01-27

Perl における配列を返す式の n 番目の要素を表す記法

例えば今,配列を返す関数があるとする.

sub f {
    (1, 2, 3);
}

f() の返り値の n 番目の要素を得るには次のようにすればよい.

@a = f();
print $a[$n];

この時,一時変数 @a を使わないようにするにはどうすればよいだろうか?

以下のようにすればうまくいくが不自然だ.

print [f()]->[$n];

あるいは,

print ${[f()]}[$n];

ともできるが,いずれにせよ不自然.

もっと素直な記法は無いのか?

勘違い (2010/7/2 追記)

上のような変なことをしなくても,

(f())[$n]

でよい.ただし,

print (f())[$n];

は print 関数と括弧の関係上文法エラーになるので,

print ((f())[$n]);

とする必要がある.上の print がうまくいかなかったので勘違いしていた .

http://d.hatena.ne.jp/metalglue/20090614/1244971305 も参考にせよ.

2010-01-26

Perl で HTML ファイルの内容を書き換える

ネットからダウンロードした Web ページの内容を少しだけエディタで書き換えることがよくある,これを Perl を使ってプログラムからやるにはどのようにすればよいだろうか.

HTML::TreeBuilder, HTML::Element あたりを使うことになるだろう.

HTML をパーズし,HTML に戻す

まずは単に HTML をパーズし,HTML に戻すプログラムを書いてみよう.

use HTML::TreeBuilder::XPath;
my $html = HTML::TreeBuilder::XPath->new_from_file('sample.html');
print $html->as_HTML('&<>');

as_HTML() の引数としてエンティティ化する文字のリストを明示的にを渡さないと,漢字などは全てエンティティにされてしまうので注意.

ここで問題となるのは $html->as_HTML('&<>') の文字コードは何なのか,そもそも sample.html の文字コードの扱いはどうなっているのかということである.HTML::TreeBuilder や HTML::Element には文字コードに関する記述は無い.

sample.html の文字コードを変えていろいろやってみたところ,どうやら入力をそのまま出力しているようである.当然 utf8::is_utf8($html->as_HTML('&<>')) は偽となる.

sample.html の文字コードを UTF-16 にするとうまくいかなかったので,結局タグが ASCII である文字コードのときにうまくいくだけのようだ.

あちこち検索などした結果,求める情報は man HTML::Parser の METHODS > Unicode にあることが判明した.これによると utf8 文字列なら扱えるとあるので,次に得られた文字列を utf8 文字列に変換する方法について書くことにする.

文字コードが不明の文字列を utf8 文字列に変換する

perl の utf8 文字列についての詳しい話は別の場所を参照してほしい.

ここでは Encode::Guess を用いて,文字コードが不明の文字列を utf8 文字列に変換する方法について書くことにする.

Encode::Guess の guess_encoding 関数を用いて以下のように書けばよい.

use Encode::Guess;

sub content_of_file {
    my ($fname) = @_;
    open my $fh, '<', $fname;
    local $/;
    my $content = <$fh>;
    close $fh;
    return $content;
}

while () {
    my $content = content_of_file($_);
    my $decoder = guess_encoding($content, qw{shift_jis euc-jp jis});
    print "$_ => ", $decoder->name(), "\n";
    my $decoded = $decoder->decode($content);
}

HTML をパーズし,HTML に戻す (again)

これまで説明してきたことを合体すれば HTML をパーズし,HTML に元の文字コードのまま戻すコードを書くことができる.

use Encode::Guess;
use HTML::TreeBuilder::XPath;

sub content_of_file { ... }

my $content = content_of_file('sample.html');
my $decoder = guess_encoding($content, qw{shift_jis euc-jp jis});
my $decoded = $decoder->decode($content);
my $html = HTML::TreeBuilder::XPath->new_from_content($decoded);
open my $fh, '>', "/tmp/sample.html";
print $fh $decoder->encode($html->as_HTML('&<>'));
close $fh;

HTML の書き換え

HTML を書き換えるには,delete_content(), push_content(), attr() などを用いる.

use utf8;

use Encode::Guess;
use HTML::TreeBuilder::XPath;

sub content_of_file { ... }

my $content = content_of_file('sample.html');
my $decoder = guess_encoding($content, qw{shift_jis euc-jp jis});
my $decoded = $decoder->decode($content);
my $html = HTML::TreeBuilder::XPath->new_from_content($decoded);

my $nodeset = $html->findnodes('//a[@href]');
for my $element (grep { $_->attr('href') =~ /^index\.html$/ &&
                        $_->look_down('_tag', 'img'); }
                 $nodeset->get_nodelist()) {
    my $href = $element->attr('href');
    $href =~ s/index\.html/default.html/;
    $element->attr('href', $href);
    $element->delete_content();
    $element->push_content('画像は削除されました.');
}

open my $fh, '>', "/tmp/sample.html";
print $fh $decoder->encode($html->as_HTML('&<>'));
close $fh;

情報源

2010-01-19

Git の根本

Git の SHA-1 名のことを調べていたら Git の根本について書いてあったのでまとめておく.簡単のため正確さを犠牲にしているので注意.

オブジェクト

Git のオブジェクトは,(type, length) というヘッダと,データ部分をつなげたバイナリである.このバイナリから SHA-1 ハッシュを計算して得られた 160 bit の値(表記は 40 桁の 16 進数)を名前とする.

オブジェクトには以下のものがある.

blob. これはファイルを表す.データ部分はファイルの内容そのものである.

tree. これはディレクトリを表す.データ部分は (type, name, object) をレコードとするリストで,name でソートされている.type に blob と tree とを取ることによりディレクトリツリーを表現できる.

commit. これはあるディレクトリツリーから別のディレクトリツリーへの変更を表す.データ部分は (before-tree, after-tree, time, author) である.

オブジェクトデータベースとリファレンス

これらのオブジェクトは全て .git/objects に格納されている.この中から所望のデータを取り出す手がかりが必要だが,それらは .git/refs/heads に commit オブジェクトの SHA-1 名を内容とするファイルとして格納されている.これらを,そのファイル名を名前とするブランチと呼ぶ.

その他

  • .git/objects に格納されているオブジェクトの名前は,ディレクトリ名とファイル名をつなげたものである.例えばオブジェクト .git/objects/4d/97bf636e78366658f2aaa80389c581e4cee9c0 の名前は 4d97bf636e78366658f2aaa80389c581e4cee9c0.
  • オブジェクトの情報,内容を表示するにはコマンド git show を用いる.
  • blob の定義から,同一内容を持つファイルのオブジェクトの SHA-1 名は常に同じとなる.
  • commit オブジェクトの before-tree は複数存在する場合がある.merge した場合におきるが,conflict のない merge の場合などには複数にならないこともあって正確にはまだよくわからない.→ git merge --squash とすると merge しても before-tree が複数にならないことが判明 hmmm...
    • 著者 JCH さんのコメントにより判明.「conflict のない merge の場合など...」は fast-forward のケースで,この場合には merge-commit とならず before-tree が複数にならない.
    • merge --squash は man git-merge の当該項目に記述してあるとおり merge-commit とならない仕様である.

情報源

入門Git pp. 8--19

Git の SHA-1 名

現在,ある Subversion リポジトリを Git によって追跡しているが,各コミットに関するベンチマーク値を Git 側のコミットの SHA-1 名をキーとして共有ディレクトリに格納している.

そこで疑問が生じるわけであるが,別の git svn clone で追跡を開始した Git リポジトリの各コミットの SHA-1 名は,元の subversion のコミットが同じなら同じになるのだろうか?それとも異なるものになるのだろうか?

予想としては,コミットというのはそもそもコミットした時の時刻情報を含んでいるので,コミット情報から生成される SHA-1 ハッシュは当然異なるものになるだろう,ということである.

しかし実験してみると,svn clone によって生成されたコミットの時刻情報は元の Subversion のコミットの時刻情報を使っているようで,SHA-1 値は全く同一となった.

2010-01-15

Git における clone の clone から origin への push

オリジナルのレポジトリ repos があるとする.

$ cd repos
$ git branch
* master

これを clone に clone する.

$ cd ..
$ git clone repos clone
$ cd clone
$ git branch
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/master
$ git checkout -b branch
$ git branch
* branch
  master
  remotes/origin/HEAD -> origin/master
  remotes/origin/master
(ファイルを編集)
$ git commit -a
$ git checkout master

これをさらに clone する.

$ cd ..
$ git clone clone cloneofclone
$ cd cloneofclone
$ git branch
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/branch
  remotes/origin/master

さて,このブランチ remotes/origin/branch を repos の branch というブランチとして push するにはどうすればよいか?

$ git push ../repos remotes/origin/branch:refs/heads/branch

とすればよい...

本当か?ブランチのネーミングの本質がわかってないので確信できない.

(2012/3/1 追記)上のやり方で正しい。ただし、refs/heads/branch と branch は同値なので、

$ git push ../repos remotes/origin/branch:branch

としてもよい。参照: Pro Git - Pro Git 9.5 Git Internals The Refspec

2010-01-13

Debian パッケージ名の規則

Q: Debian のパッケージ名の規則はどこに書いてあるのか?

ADebian Policy Manual - Control files and their fields (5.6.1 Source) にある.誰もが 3.1 The package name に書いてあると思うだろうが,そこの参照先の参照先である上記に記述してある.

これによると,[a-z][a-z0-9.+-]+ である.