3番目には、知的所有権に関する英国委員会が、オープンソースと専用実施権制限が、開発途上国の将来発展に肝要essential to developing countries future progressとの結論に達したconcluded 。この報告は的by Linux Weekly News と by the Economistもまた取り上げている。
X-Boxを持つ人は、Debianを走らせると良いmay want to try running Debian on it。
Century Software は、TinyTERM Version 4.3. のリリースを発表した。TinyTEAMにより、Windows PCを容易に正確に文字ベースのデータ及びUNIX、Linux及びIBMがホストするアプリケーションに使用することが出来る。これは、エミュレーション20個、ネットワークユティリティ、ソースファイル転送及び無料技術サポートを含む。TinyTEAMは、ダウンロードで入手することが出来るavailable for download。
AlphaWorks は、C及びC++アプリケーションをSolarisからzシリーズLinuxに移植出来るようにして、Linuxプラットホームに対するアプリケーション開発速度を上げる無料移植マネージャ "porting manager" を提供した。
By Paul Evans
ユーザ入力を必要とするプログラムを書く遙か前まで、Python を使おうと考えたことはないだろう。好奇心に満ちたPythonプログラム初心者として、君は簡単にユーザーに入力を頼み、その通りして呉れると信じているだろう。
ユーザーはそんな風にしてくれない。
例えば、「イエス」「ノー」の返事を求めたとしよう。ユーザは丁寧に名前をタイプしたり−昼のおかずを入力したり、何もしないかも知れない。挙げ句に、プログラムが故障する。(殆どは)わざとしているのではない。哀れな奴が気が散って君が丁寧に催促した言葉を無視して、プログラムに関する限り頓珍漢なことをタイプするのだ。続いて、奴らはプログラマに文句を言う。君は、馬鹿だと思われて落ち込む。
この悲劇を避けるには、先ず、ユーザの背後にあるものは何でもチェックして、微かにでも自分の求めるものに近いことを確かめる必要がある。Pythonには、これを助ける機能が多数あるので、以下でこれを経験して頂く。
別に出来ることは、入力ウイジェットにバリデータを使うことだ。この作業の方法は、追随しないキイ打ちを投げ出すことだ。例えば、文字列ウイジェットに数値バリデータを設定すると、ユーザーは"ABC"などを好きなだけ打てるが何も起こらない。有効なのは多分10進数又は金額の0-9だけだ。ずっと後でこれを試す。
最後に、自分が望む通りの入力をしてくれる良く訓練された素直なユーザに運良く出会ったときであっても、入力は望み通り正確な形式になっているとは限らない。不注意で「jOHN sMith」(大文字ロック)とタイプしたり電話番号を「604555-1212」とすることもある。
冗談はさておき、ユーザが出来るだけ早く正確にデータを入力出来て、矛盾のない形式で提示され記憶されるようにするのは、プログラマの仕事だ。それに、自分も満足することが出来る。
入力の取得
先ずプログラムがユーザ入力を取得する必要がある。このためPythonはコンソールから二つのメソッド'raw_input("Prompt")' と 'input("Prompt")'を提供する。('input'を使わないこと、以下を参照)。入力は古き良きコマンド行引数又は環境変数から取得することも出来る。
その他、余り興奮しないで、 Xdialog, Gdialog (gnomeの一部)又は Kaptainなど、もっとグラフィックな方法も利用することが出来る。
完全なGUIキットへのアクセスは、PythonからPyQT , TKinter, WxPython 及び PyGTKなどを使ってアクセスすることが出来る。
ここで一寸注意しておくのが良いだろう。殆どのユーザは、満足した素直な生物だが、中にはで、 ぶち壊しの好きな悪党もいる。
その理由で、ユーザ入力がコマンドスペースに入るのを許してはならない:
気味の悪い場所はこれで終わりだ。
xtermを開いて 'python' とタイプしインタプリータに入る。注意:これらの例の多くは、Pythonのバージョン2以降を使う必要がある。Redhat は未だに規定値で1.5xを出荷しているので、Redhatユーザは、代わりに'python2'とタイプする必要がある(それに多分先ず'add-ons'からrpmをインストールする必要がある。念のため云うと、バージョン1.5は、数字1と9から始まる年にリリースされた。
文字列オブジェクトの内容チェック
プログラム言語には、通常この種のチェックのための方法が含まれ、Pythonも例外ではない。上述のような最初の課題:一つを要求したときユーザが有効な数字を与えたかを確かめること、を考える。
Pythonの中の文字列オブジェクトはすべて、thを簡単にする埋込メソッドを有する。以下の行を'>>>' プロンプトで入力する。
>>>input = '9' >>>input.isdigit() 1
これは、'1' (真)を返すので、'if' ステートメントの中で条件として簡単に使うことが出来る。この種の別の属性は:
s.isalnum() は、文字全部が英数字のとき真を、さもないとき偽を返す。 s.isalpha() は、文字全部が英字のとき真を、さもないとき偽を返す。
これら及び、その他多くのリストは、 Python 2.1 Quick Referenceを強く推薦する。私は常にこれを用い、速度を上げるため HNB に入った古いテキスト版まで持っている。
これはメニュー選択など簡単な場合に使えるが、浮動小数点や実数が欲しいときはどうだろうか?
次を考える:
input = '9.9' 又は input = '-9'
両者とも有効な数字だが、input.isdigit()は'0'(偽)を返す。負号と小数点は、'digits'でないからだ。哀れなユーザはエラーメッセージを貰って慌てるだろう。
そこで、それらを明白に転換して試したいものであるとしよう。そのため、Pytonのtry/except 構造を使う。Pythonは、エラーに関して各種の例外を起こすので、これらのエラーを名称によって個別に捉えることが出来る。
'-9'のような整数が欲しいとしよう。数値演算子'int()' を使って、転換を明白におこなわせることが出来る。
次を試す:
someVar = int(input)
print 'Is an integer'
except (TypeError, ValueError):
print 'Not an integer'
ここで二つの事に注目する。第一は、二つの異なる例外、型と値、をチェックしていることである。こうして、ユーザーが浮動小数点('9.9'など)を入力するのを扱うだけでなく、どんな種類の数値も入力しない可能性−ハムサンドと入力するかも知れない−をも許す。注目する第二の事柄は、捉える興味のある種類の例外を実際に入力したことである。どんなエラーを捉えるかに悩むことなくオープンエンド例外をタイプするだけは次のように極めて容易である:
次を試す:
someVar = int(input)
print 'Is an integer'
except:
print 'Not an integer'
これをしてはいけない。Pythonでは出来るが、例外すべてを捉えようとしているので、全部が壊れるなら、デバッグは悪夢であろう。thが一つであることに私を信じららたい;捉え用とするエラーを見ると、長い目で見て時間を節約することが出来る。
有用と思うその他の演算子は、long()とfloat()である。flip 側では、str() が何でも文字列に転換する事が出来る。
レンジチェックを忘れてはいけない−プログラムが整数を常に入手するのを確かめて喜んではいけない、月の日数に'42'を呑気に受け入れているかも知れない。演算子 '>, <, >=' などをつかって、数値が予期範囲に入っているかどうか確かめること。
入力の検証
見て来たように、入力を獲得した後それを検証することは出来るが、ユーザは最初に間違うのを防止することが出来ると良い。
ウィジェット・バリデータを入力する。
不要なキイストロークが文字列ウィジェットに現れるのを防止するグラフィック・ユーザインターフェイス・ツールキットに組み込まれたものがある。ツールキットは、通常、数値、英字、及び英数字などの埋込バリデータと一緒に来るので、容易く使用出来る。私は現在、GUI用に PyQT を使っているが、TKinter, WxPython 及び Kaptain にもバリデータがある。間違っているかも知れないが、 PyGTK には、未だ無いように思われる。これらの無いツールキットを使うときは、多分信号をフックアップしてロールすることが出来る。
埋込バリデータがPyQtに合わないとき、例えば自分で、特注バリデータを、規定することが出来る。
明らかに、ここですべてのツールキットに言及することは出来ないが、数値バリデータをpYQTの中のウィジェットに付着する方法を示す。ウィジェットの名称は'self.rate'で、'QDoubleValidator'を付着して、10進2桁までの場所で0.0と999.0の間の数値を受け入れるよう命じる:
self.rate.setValidator(QDoubleValidator(0.0, 999.0, 2, self.rate) )
どうだろう?レンジチェックもすることに注意!
ユーザが情報を入力するのを助ける別の方法は、 spinners, pick-lists 及び combo-boxesであるが、既に分かっている筈。
入力のフォーマット
緒言の'jOHN sMith' の例を思い出されたい。修復方法を示す:
>>>'jOHN sMith'.title() 'John Smith'
そう、Pythonの文字列オブジェクト全体の別の属性は 'title()' 属性で、これが各語を大文字で始める。'capitalize()' も同様だが、最初の文字だけに働く:
>>> 'jOHN sMith'.capitalize() 'John smith'
先に進んで、お望みなら 'upper()', 'lower()' 及び 'swapcase()' も試されたい。作用は推測出来る筈。
だが'rjust(n)' はどうだろうか?これは、レポートのレイアウトに使うことの出来る唯一の手軽な属性だ。下記を見られたい:
>>> 'John Smith'.rjust(15) ' John Smith'
この文字列は、15文字の長さで右にジャスティファイされた。多分想像されるように、 'center(n)' と 'ljust(n)' もある。ここでも、Python 2.1 Quick Reference で全体を見られたい。
Pythonで重要な別の演算子は、'%' (パーセント) 演算子だ。リストオブジェクト及びprintf形式フォーマットコードの関連するこれの説明には数頁を必要とするので、ここでは、少数の例を示してで興味をそそるだけで勘弁して貰う。
最も簡単な形で '%' 演算子は、実行時に変更することの出来る変数を含む正式の文章を書くことが出来るようにする:
>>> 'This is a %s example of its %s.' % ('good', 'use')
'This is a good example of its use.'
少なくとも、そうあって欲しい。これは能力のほんの入り口だ。 '%s' に文字列オブジェクトを代入するのに加え、'%r' 及び'C'言語からのprintf仲間:c, d, i, u, o, x, X, e, E, f, g, G.もある。
Python 2.1 Quick Referenceから一例を示す:
>>> '%s has %03d quote types.' % ('Python', 2)
'Python has 002 quote types.'
右側はマッピングでもよい。それによりフィールドを名称で示すことが出来る。
少し難しいが、普通に使う事柄に進もう。
電話番号
電話番号は長さが変化する。内線電話では2、3桁のこともあるし、国際電話では15桁にも達する。'#'や星印、さらにはコンマを含む事もある。最悪の場合、ユーザーは入力書式を決めることもあるし、部分的に決めたり決めなかったりもする。
少なくとも正しく入力しようとさせないとユーザに不満が残る。バリデータは0-9の数字と同時に、#, *, コンマ, -, ), ( のすべてを受け入れ、
文字列:
'(250) 555-1212' の代わりに:
実際に(北米電話番号について)必要な
'250-(555)-12-12'
で終わらせる。何にでも通用する一般的対策を作るので、御心配なく。
このような問題に直面したときの第一感は Google - 特に Google Groupsで人様の作品をコピイすることだった。見付かったコードは常に私が作るものより遙かに優れていたので、良い考えだった。残念乍ら、今回はGuido van Rossum (Pythonの発明者)から、Pythonにはこのようなものがない、多分次のように出来るだろうとe-メールを貰った:
import string
def fmtstr(fmt, str):
res = [] i = 0
for c in fmt:
if c == '#':
res.append(str[i:i+1]) i = i+1
else:
res.append(c)
res.append(str[i:])
return string.join(res)
これは良いスタートで作者の信用に文句はつけられないが、何桁の数字を与えられたかを勘定して正しい長さの書式文字列を選らぶには、'if/then' を沢山使わないとすべての場合を扱うことは出来ない。先に進みこれを xterm に貼り付けて次のように呼び出そう:
>>> fmtstr('###-####', '5551212')
'5 5 5 - 1 2 1 2 '
事実、私はこれをコピイしてエディタに貼り付け、電話番号、日付その他も型の入力のため長たらしい 'if/thens' を構築したが、それでも全部は扱えない。その上、似たことをおこなうのに膨大な行が出来上がった。そこで、あきらめた。
おこなった方法を示す。。先ず、ユーザにタイプさせた「余計な」書式文字を篩にかける:
def filter(inStr, allowed):
outStr = ''
for c in inStr:
if c in allowed:
outStr += c
return outStr
これを次のように呼び出すことが出来る:
>>>filter('250-(555)-12-12', string.digits)
'2505551212'
又は、第二引数を自分で '0123456789#*,' と定義して、許容出来る文字全部を含ませる。
ここで、Guidoのコード断片を取り上げ両入力因数を逆にする。この方法で、長い書式文字列を一つだけ規定することが出来、入力の外に出るまで整合される。余分の入力が付け加わるので、文字を失うことはない。
# 規則的表現モジュールのインポート
import re
def formatStr(inStr, fmtStr, p = '^'):
inList = [x for x in inStr] #list from strings..
fmtList = [x for x in fmtStr]
# the good bit
inList.reverse(); fmtList.reverse()
outList = []
i = 0
for c in fmtList:
if c == p:
try:
outList.append(inList[i])
i += 1
# break if fmtStr longer than inStr
except IndexError:
break
else:
outList.append(c)
# fmtStr より長い inStr の扱い
while i < len(inList):
outList.append(inList[i])
i += 1
# これを見出した方法で元に戻す
outList.reverse()
outStr = ''.join(outList)
# 迷走 parens/- などを削除
while re.match('[)|-| ]', outStr[0]):
outStr = outStr[1:]
# close any legit parens
while outStr.count(')') > outStr.count('('):
outStr = '(' + outStr
return outStr
[Text version of this listing.](このリストのテキスト版)
これは、Guidoのものと同じだが、'#' を使う必要があるため、規定値位置ホルダ文字がここでは '^' (カレート)になっている点だけが異なる。代わりに、出力に実際のカレートを必要とするときは、任意選択の第三引数として規定することも出来る。
出力のサンプルを幾つか示す:
>>> formatStr('51212', ' ^^^ ^^ (^^^) ^^^-^^^^')
'5-1212'
>>> formatStr('045551212', ' ^^^ ^^ (^^^) ^^^-^^^^')
'(04) 555-1212'
>>> formatStr('16045551212', ' ^^^ ^^ (^^^) ^^^-^^^^')
'1 (604) 555-1212'
>>> formatStr('1011446045551212', ' ^^^ ^^ (^^^) ^^^-^^^^')
'1 011 44 (604) 555-1212'
実用では、自分の電話番号書式文字列だけを早くから決めるのが良いだろう。例えば:
phone_format_str = ' ^^^ ^^ (^^^) ^^^-^^^^'
文字列の初めに空白があって、追加文字が入れられる。次のように呼び出す:
... 'filter()' ファンクションなどを用いて「入力」を掃除した後、
formatStr(input, phone_format_str)
郵便番号
カナダ郵便番号を知らない人のため言うと、次の形になっている:
'V8G 4L2'
タイプするまでは、何の変哲もない。殊にタイプの下手な人は、大文字ロックを掛けないと−偶に解除を忘れる−[shift]+英字, 数字, [shift]+英字などとタイプし、手順から出るとき多くは'v*g $l@'で終わる。言うまでもないが、ユーザーはこの入力を嫌い、正しいことは少ない。申し込みの多くには、ユーザを悩まさないため、郵便番号を受け付けないものもある。似たような郵便番号の国もある。
ここで、我々の書式ファンクションを使うと、お茶の子さいさいだ。先ず、入力を検証するつまり篩に掛ける。次いで、Pythonの埋込属性 'upper()' を用いて、英字の大きさを正しくする。最後に:
>>>formatStr('V8G4L2', ' ^^^ ^^^')
'V8G 4L2'
正確な郵便番号が申し込みに取って重要なときは、文字を勘定しパターンを検証する方法で、さらに検証する必要がある。だが、一般用途のため、他国の郵便番号も受け入れなければならない。クリーンアップ後の文字数=6の場合だけを考える。
社会保険番号も同様に扱う:
>>> formatStr('716555123', '^^^-^^^-^^^')
'716-555-123'
社会保険番号には先ず check digit ルーチンを走らせて、有効なことを確かめる。クレジットカードも同様だ。
これらの例がユーザインターフェイスのコードに役立つことを願う。ご意見や提案を歓迎するhear back。特に日付に付いて然り。これは常に面白い。
閑話休題、これら書式援助はユーザーには秘密に保つのが重要だ。「ヘルプ」に置いて、機能がそこにあることを知らせるには「ツールキット」又は「作用説明」を用いる。ユーザがサンザン苦労した後に、それを発見したら、ブーブー言うのが落ちだが、澄ました顔をしていれば良い。
この記事は、Makefileを作るときの指針として使えるものを提供する。この記事は、Makefileの必要性とMakefileを作るとき考慮すべき事項を述べる。
緒言
foo と言うプログラムを開発しているとする。それは、1.h, 2.h, 3.h, 4.h, 5.h ヘッダ五つと、1.cpp から 5.cppまでのC言語ソースコード六つと、main.cpp ファイルを含んでいるとする(実際にはこのようなファイル名称計画は使わない方が良い)。
1.cpp にバグを見付けて修正したとする。新しい foo プログラムを得るには、ファイル一つだけ変更したときも、ヘッダーやソースコードなどファイル全部をコンパイルし直さなければならない。これは、コンピュータがコンパイルするのを待つだけの、特にコンピュータの速度が遅いときは、退屈な作業だ。
解決策はないものか?
御心配なく。この種の問題はコンピュータ・ベテランが数年前に解決した。解決策として make と言う名のプログラムを作った。これはソースコード全部を構築する代わりに変更されたソースコードだけを構築する。file 2.cpp を変更したら、make はそれだけを構築する。面白いだろう。
以下が make を必要とする別の理由だ [2] :
Makefileを必要とする理由
ソースコード構築に当たっての問題を make が解決すると思ったら、もう一度考え直されたい。make は、プログラマが命令を与えないと何も出来ないからだ。make は make に与えるコマンドを含む別のファイルが付属しなければならない。それを makefile と呼ぶ。
このファイルは makefile 又は Makefile と名付ける。便宜のため、GNUプログラムには、探し易いので Makefileと言う名の makefile がある("ls" を実行すると、このファイルは普通常に先頭になる)。これを別の名で呼ぶと、そのファイルを使うことを知らせるには、make オプション -f を与えなければならない。
例えば、makefile の名を bejo とすると、このファイルに対し make を走らせるコマンドは、次のようになる:
make -f bejo
Makefile 構造体
Makefileは、target、dependencies 及び rules のセクションを含む。Dependecies は、target を作るのに必要なものとソースコードで:target は通常実行可能ファイルである。Rules は、target を作るのに必要なコマンドである。
下記に makefile の簡単な記述を示す。
target: dependencies command command ...
Makefile の一例
以下は簡単な makefile の一例である(記事のため行番号を追加):
1 client: conn.o 2 g++ client.cpp conn.o -o client 3 conn.o: conn.cpp conn.h 4 g++ -c conn.cpp -o conn.o
上の makefile において、dependencies は client: conn.o を含む行で、rules は、g++ client.cpp conn.o -o client. を含む行である。各 rule l行がタブで始まることに注意。これはタブであって空白ではない。タブを忘れたり空白を用いたりするのは、makefile を構築するとき、起こし易い間違いだが、タブがないと makefile は働かない。
上の makefile は次のように説明することが出来る:
コメント
makefile にコメントを入れるには、単に各行の第一列に '#' を置いてコメントする。
下記はコメントを入れた makefile の一例だ:
# "client" と言う名の target を作る 1 client: conn.o 2 g++ client.cpp conn.o -o client # Creating object file "conn.o" 3 conn.o: conn.cpp conn.h 4 g++ -c conn.cpp -o conn.o
Phony Target [1]
phony target とは仮の名である。これは、明白な要求をするときに実行されるコマンドだけのための名である。phony target を使う理由は二つある:同じ名のファイルとの混同を避けるためと、makefile にファイル作成以外の仕事をさせるためである。
そのコマンドが target ファイルを作らない rule を書くと、これらのコマンドは target を作り直す毎に実行される。例えば:
clean: rm *.o temp
コマンド rm は clean と言う名のファイルを作らないので、このファイルが存在することはない。コマンド rm は、clean を呼び出す毎に実行される。clean ファイルが常に新しいことを確かめるため、実行する必要があるからだ。
phony target は、誰かが現在ディレクトリの中に clean と言う名のファイルを作ると、働きを止める。dependencies を必要としないので、ファイル clean は、更新されたと見なされて、'rm' コマンドは実行されないからだ。この問題を解決するため、特殊 target .PHONY を用いて target を明白に phony と宣言することが出来る。例えば :
.PHONY : clean
上の makefile で clean は、は clean と言う名のファイルがあろうがなかろうが、常にclean コマンドを走らせる。
変数
makefileで変数を定義するには、次のコマンドを用いる:
$VAR_NAME=value
変数名は通常大文字で与える。例えば :
$OBJECTS=main.o test.o
変数の値を得るには、次のように、変数名の前に記号 $ を置く :
$(VAR_NAME)
makefile には、二種類の変数がある。繰り返し拡張変数(recursively expanded variable)と単純拡張変数(simply expanded variable)である。
繰り返し拡張変数では、make は拡張不能になるまでその変数を拡張し続ける。例えば:
TOPDIR=/home/tedi/project SRCDIR=$(TOPDIR)/src
SRCDIR 変数は、先ず TOPDIR 変数を拡張することにより、拡張される。最終結果は、/home/tedi/project/src である。
しかし、繰り返し拡張変数は、次のコマンドには適当でない:
CC = gcc -o CC = $(CC) -O2
繰り返し拡張変数を使うと、これらのコマンドは無限ループに入る。この問題を解決するため、単純拡張変数を用いる:
CC := gcc -o CC += $(CC) -O2
を読まれたい。http://www.linuxdoc.org/ から入手することが出来る。
1.環境
e-メール環境を定義する:e-メールアカウントが三つある。それぞれ別のサーバーに置かれている。これらを「第一」「第二」「第三」と呼ぶ。それらのアドレスは、first@firstdomain.com, second@seconddomain.com, third@thirddomain.comで、第一アカウントは IMPプロトコルを使い、その他は POP3 を使っている。
メッセージ全部を受け取るローカルユーザは「john」である。(不安全で不便な)規定値 '/var/spool/mail/john' は使わないので、この人は、 $MAIL 環境変数用に新しい値を設定する必要がある。これをおこなうため、.bash_profile に次の行を追加する(別のシェルを使うなら、勿論、別のものを変更しなければならない):
MAIL=$HOME/Mail/Inbox export MAIL
(ディレクトリ'$HOME/Mail' を作るのを忘れないこと!)。メッセージ読取用に追加のメッセージボックスを使用する(各アカウントに関連ボックスがある)。
2.Fetchmail
メールを読む前に、リモートサーバーから取り出さなければならない。それには、fetchmail と言うツールを使う。システムにはインストールされている筈だ。
fetchmailのコンフィギュアは極めて簡単だ。その上'fetchmailconf' を使うことが出来るので、更に容易になる。編集すべきコンフィギュレーションファイルは、$HOME/.fetchmailrc だ。簡単なもので、我々の環境に適切なものは次のようになる:
set postmaster "john"
set bouncemail
set properties ""
set daemon 300
poll First via firstdomain.com
with proto IMAP
user first there with password this_is_password is john here warnings 3600
poll Second via seconddomain.com
with proto POP3
user second there with password this_is_password is john here warnings 3600
poll Second via thirddomain.com
with proto POP3
user third there with password this_is_password is john here warnings 3600
fetchmail を走らせるには、fetchmail とタイプするだけだ。デーモンモードでスタートし、5分毎に新メールの到着をチェックする。
3.Mutt
メッセージがローカルマシンに入ったので、任意のメール・ユーザー・エージェントを使って読むことが出来る。この記事はmuttを扱うので、これはmuttであるとする。
Muttを好きなように働かせるには、コンフィギュアしておく必要がある。先ず、そのコンフィギュレーションファイル(通常 $HOME/.muttrc と言う)に幾つかの基本定義を入れなければならない。次のようになる:
set mbox = "~/Mail/Inbox" set move = no set folder = "~/Mail" set record = +Sent mailboxes +Inbox +First +Second +Third
これで実際にメールを読むことが出来るが、各出信メッセージのFromヘッダフィールドに john@localhost のようなものが入る。発信主アドレスを変更して、メッセージが firstdomain.com か seconddomain.com 又はアカウントのあるマシンのどれかからか、分かるようにしなければならない。
その目的で、追加メールボックス(第一、第二、第三)と、mutt の所謂るフック機構を使う。後者は、何かの作用が実行されたとき、ユーザ定義コマンドを実行する。ユーザが('c'キイを押して)メールフォルダを変えたとき呼び出される folder-hook がある。From フィールドを変更するには、from と ralname の mutt 変数を変えなければならない:
# Default action: folder-hook . set from = first@firstdomain.com folder-hook . set realname = First # First account: folder-hook First set from = first@firstdomain.com folder-hook First set realname = First # Second account: folder-hook Second set from = second@seconddomain.com folder-hook Second set realname = Second # Third account: folder-hook Third set from = third@thirddomain.com folder-hook Third set realname = Third
We should also define the alternates variable so mutt can recognize messages sent to and by us:
set alternates = "first@firstdomain\.com|second@seconddomain\.com|third@thirddomain\.com"
注記: http://mutt.netliberte.org/ にMuttrcBuilder と言うツールがある。muttのコンフィギュアには、これを使うことが出来る。
コピイをし終わると−100Mb/sでも時間が掛かる−Ymir上で "chroot /mnt/0 /bin/bash" をおこなった。これで、新ファイルシステムがルート("/")になり、そのシェル上で走る。旨くいった!「道程の4分の3」だと思った。重要なことで残っているのは、システムをブートすることが出来るようにすることだ。勿論システム特有の設定をしなければならない。"/etc/fstab" を修正して正しいパーティションを反映させること、"/etc/modules" を直して正しいものをロードすること、"/etc/lilo.conf" を調整すること、"/sbin/lilo" を走らせて正しい画像をブートすることだ。あっ・・・・。Baldurは、"initrd"経由でブートする。"initrd" 画像には、ルートとして「ピボット」されるパーティションの情報が含まれており、これはBaldurとYmirでは異なる。そこで、Baldurにジャンプして、 "mkinitrd" を "-r /dev/hda2" オプションで走らせた。これで新"initrd"画像がルートとしての正しいパーティションで構築される。これをYmirに"rsync"したら、旨くいくように思えた。
再ブートまでの最後のステップとして、"/sbin/lilo -v3"を走らせたら・・・おやおや、LILOの前のバージョン(そこにあった旧式のDebianディストリビューション)でブートセクタを作ったとのメッセージが出て、新しい方の書き換えが拒否された。普通そんな事はない筈だ。LILOのマニュアルには、初期インストールのためだけに使う筈の"QuickInst" スクリプトが説明してある。(LILOの"doc"ディレクトリ参照)。"QuickInst" はベーシックの設定中に働いており、私は簡単に書かれる "lilo.conf" に手を加えなかったので、私の設定に一致しかなかったのだ。これがブートセクタと書き直してしまったのだ。そこで "lilo.conf" の正しいバージョンを"/etc"にコピイし直し、あらためて"lilo -v3"を走らせた。
そこで再ブートし、BIOS設定を交換して、YmirがCDROMでなくHDからブートするようにした。少しエラーが出るのを見守っていると、ログインプロンプトが出た。これで九分通り完成だ。あと二つだけ。念のため言うと、エラーを生じたのは、"hdparm" ディスク同調パラメータを変更しなければならないこと、および古いモジュール一つが未だ"/etc/default/hotplug.usb"からロードしていたことだ。それ以外の残りは新システムの設定に似ているが、作業は少ない。見てみよう。
参考書
$mygraph = GD::Graph::chart->new($width, $height);
$mygraph = GD::Graph::bars->new($width, $height);
$myimage = $mygraph->plot(\@data);
簡単な例
上述のステップにしたがって簡単なチャートを書いて見よう。このスクリプトは、画像をウエブペイジに出力するのに CGI を用いる。
(このリストのテキスト版)[Text version of this listing.]
#!/usr/local/bin/perl -w
# 上の行を自分の perl バイナリを指すよう変更する
use CGI ':standard';
use GD::Graph::bars;
use strict;
# 両アレーのエンリトリは同数でなければならない
my @data = (["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug",
"Sep", "Oct", "Nov", "Dec"],
[23, 5, 2, 20, 11, 33, 7, 31, 77, 18, 65, 52]);
my $mygraph = GD::Graph::bars->new(500, 300);
$mygraph->set(
x_label => 'Month',
y_label => 'Number of Hits',
title => 'Number of Hits in Each Month in 2002',
) or warn $mygraph->error;
my $myimage = $mygraph->plot(\@data) or die $mygraph->error;
print "Content-type: image/png\n\n";
print $myimage->png;
プログラムの出力はここ here で見られる。
上のプログラムは、ほとんど自明であろう。@data 変数はアレーのアレーである。最初のアレーはX-軸のラベルをあらわし、それに続く残りのアレーは全部別のデータセットをあらわす。
オプションの採用
お分かりのとおり、上のプログラムが作るグラフは、平凡で簡単だ。オプションを工夫してグラフの見掛けを好みに合わせることが出来る。様々なオプションがあって、グラフの色々な側面を好きなように変えることが出来る。オプションは二つの型に分かれる。あらゆる型のグラフに共通なオプションと、グラフの各型に特有のオプションだ。
オプションはグラフを作っている間又は下記で設定することが出来る
$mygraph->set(attrib1 => value1, attrib2 => value2, ...);
凡例、グリッド及び幾つかのオプションのスクリプトを書いて見よう。
(このリストのテキスト版)[Text version of this listing.]
#!/usr/local/bin/perl -w
# 上の行を自分の perl バイナリを指すよう変更する
use CGI ':standard';
use GD::Graph::bars;
use strict;
# 両アレーのエンリトリは同数でなければならない
my @data = (['Fall 01', 'Spr 01', 'Fall 02', 'Spr 02' ],
[80, 90, 85, 75],
[76, 55, 75, 95],
[66, 58, 92, 83]);
my $mygraph = GD::Graph::bars->new(500, 300);
$mygraph->set(
x_label => 'Semester',
y_label => 'Marks',
title => 'Grade report for a student',
# 幅3ピクセルの棒を描く
bar_width => 3,
# 棒を4ピクセルで分離する
bar_spacing => 4,
# グリッドを示す
long_ticks => 1,
# それぞれの棒の頭部に値を示す
show_values => 1,
) or warn $mygraph->error;
$mygraph->set_legend_font(GD::gdMediumBoldFont);
$mygraph->set_legend('Exam 1', 'Exam 2', 'Exam 3');
my $myimage = $mygraph->plot(\@data) or die $mygraph->error;
print "Content-type: image/png\n\n";
print $myimage->png;
上のプログラムの出力はここ here で見られる。
背景にロゴを入れたグラフ
ここでも、GD::Graph が好みのグラフを作る方法を与えるのが分かる。ロゴの付いた別のチャートを作って見よう。
このファイルのテキスト版はここ here 。
#!/usr/local/bin/perl -w
# 上の行を自分の perl バイナリを指すよう変更する
use CGI ':standard';
use lib '/cise/homes/ppadala/mydepot/lib/perl5/site_perl';
use GD::Graph::bars;
use strict;
# 両アレーのエンリトリは同数でなければならない
my @data = (['Fall 01', 'Spr 01', 'Fall 02', 'Spr 02' ],
[80, 90, 85, 75],
[76, 55, 75, 95],
[66, 58, 92, 83]);
my $mygraph = GD::Graph::bars->new(500, 300);
$mygraph->set(
x_label => 'Semester',
y_label => 'Marks',
title => 'Grade report for a student',
# 幅3ピクセルの棒を描く
bar_width => 3,
# 棒を4ピクセルで分離する
bar_spacing => 4,
# グリッドを示す
long_ticks => 1,
# それぞれの棒の頭部に値を示す
show_values => 1,
) or warn $mygraph->error;
$mygraph->set(logo => 'lglogo.png');
$mygraph->set(logo_resize => 0.5);
$mygraph->set(logo_position => 'LL');
$mygraph->set_legend_font(GD::gdMediumBoldFont);
$mygraph->set_legend('Exam 1', 'Exam 2', 'Exam 3');
my $myimage = $mygraph->plot(\@data) or die $mygraph->error;
print "Content-type: image/png\n\n";
print $myimage->png;
上のプログラムの出力はここ here で見られる。
ここでは Linux Gazette logo を使った。これは PNG フォーマットである。GD::Graph の現行版は、GIF以外の型の画像を認識しない(PNGは描くけれども、go figure)。これを直すため patch を送った。patchを適用するか又は古い版の GD 又は GD::Graph を使うと良い。
線を用いるグラフ
情報によっては線でグラフを示すのが良い。線グラフの例を示す。
(このリストのテキスト版)[Text version of this listing.]
#!/usr/local/bin/perl -w
# 上の行を自分の perl バイナリを指すよう変更する
use CGI ':standard';
use GD::Graph::lines;
use strict;
# 両アレーのエンリトリは同数でなければならない
my @data = (['Fall 01', 'Spr 01', 'Fall 02', 'Spr 02' ],
[80, 90, 85, 75],
[76, 55, 75, 95],
[66, 58, 92, 83]);
my $mygraph = GD::Graph::lines->new(600, 300);
$mygraph->set(
x_label => 'Semester',
y_label => 'Marks',
title => 'Grade report for a student',
# データセットを'実線', '破線' 及び '点線' で描く
line_types => [1, 2, 4],
# 線の太さを設定
line_width => 2,
# データセットのための色を設定
dclrs => ['blue', 'green', 'cyan'],
) or warn $mygraph->error;
$mygraph->set_legend_font(GD::gdMediumBoldFont);
$mygraph->set_legend('Exam 1', 'Exam 2', 'Exam 3');
my $myimage = $mygraph->plot(\@data) or die $mygraph->error;
print "Content-type: image/png\n\n";
print $myimage->png;
上のプログラムの出力はここ here で見られる。
ここでは GD::Graph::lines を使ってグラフハンドルを作った。だが、この変更のため、プログラムはグラフを作るのと同じパターンをたどる。
円グラフ
同じようにして円グラフを作ることが出来る
(このリストのテキスト版)[Text version of this listing.]
#!/usr/local/bin/perl -w
# 上の行を自分の perl バイナリを指すよう変更する
use CGI ':standard';
use GD::Graph::pie;
use strict;
# 両アレーのエンリトリは同数でなければならない
my @data = (['Project', 'HW1', 'HW2', 'HW3', 'MidTerm', 'Final'],
[25, 6, 7, 2, 25, 35]);
my $mygraph = GD::Graph::pie->new(300, 300);
$mygraph->set(
title => 'Grading Policy for COP5555 course',
'3d' => 1,
) or warn $mygraph->error;
$mygraph->set_value_font(GD::gdMediumBoldFont);
my $myimage = $mygraph->plot(\@data) or die $mygraph->error;
print "Content-type: image/png\n\n";
print $myimage->png;
円グラフの出力はここ here で見られる。
'3d' オプションは円グラフを3次元で描く。
面積グラフ
面積グラフはデータを線の下の面積として示す。
(このリストのテキスト版)[Text version of this listing.]
#!/usr/local/bin/perl -w
# 上の行を自分の perl バイナリを指すよう変更する
use CGI ':standard';
use GD::Graph::area;
use strict;
# 両アレーのエンリトリは同数でなければならない
my @data = (["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug",
"Sep", "Oct", "Nov", "Dec"],
[23, 5, 2, 20, 11, 33, 7, 31, 77, 18, 65, 52]);
my $mygraph = GD::Graph::area->new(500, 300);
$mygraph->set(
x_label => 'Month',
y_label => 'Number of Hits',
title => 'Number of Hits in Each Month in 2002',
) or warn $mygraph->error;
my $myimage = $mygraph->plot(\@data) or die $mygraph->error;
print "Content-type: image/png\n\n";
print $myimage->png;
出力画像はここ here で見られる。
結語
GD::Graph モジュールはチャートを作るための協力で融通性に富んだ方法を提供する。ウェブ上で役立てるためグラフを動的に作るのに極めて有用である。
この記事がお役に立ったことを祈る。来月は、PerlMagic モジュールを学ぶ。
この問題の新人へ
SNMPに対する新人は下記を参照されたい。
Net-snmp
Network Management Protocol に関する各種ツールは:
NET-SNMP site を参照。
Snmptrapd
Snmptrapd は、SNMP-TRAP ポート(162)に送られれた snmp トラップメッセージを受信し記録する SNMPアプリケーションである。これは snmp トラップ受信に際し特殊プログラムを走らせるためコンフィギュアすることが出来る。
snmptrapd.conf
snmptrapd.conf は、トラップを受信したとき、SNMPトラップ受信デーモンの働く方法を定義するコンフィギュレーション・ファイルである。
ups−MIB
RFC1628 文書は、単純ネットワーク管理プロトコル(Simple Network Management Protocol (SNMP))経由で管理することの出来る無停電電源装置用管理オブジェクトを定義する。
Powerh を用いる snumptrapd の使い方
注記:'powerd' を'powerh' と言い換えたのは、ここではこれはデーモンではなくトラップ取扱ルーチンに過ぎないからである。
powerhを用いてシステムの電源状態を取り扱って来た。powerhは、シリアルポートを通じてUPSと連絡する。しかし、多数のマシンがUPSを使うネットワークシステムでは、各システムがUPSを使って直接連絡することは出来ない。最新高容量UPSは、直接又はプロキシを通じるのいずれかでSNMPプロトコルをサポートする。各種電力状況の取扱いは下記のステップにしたがう。
1. 自分の snmptrapd.conf に次の行を追加する
traphandle 33.2.3 powerh b traphandle 33.2.4 powerh p
2. cc powerh.c -o powerhand copy powerh を入力して、 /usr/local/sbin/などのパスにあるディレクトリに、次の C code をコンパイルする。
(このリストのテキスト版)[Text version of this listing.]
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#define PWRSTAT "/etc/powerstatus"
void powerfail(int);
main(int argc, char* argv[]) {
char s[1000];
int i=0;
while(i<7) {
scanf("%s",s);
i++;
}
scanf("%s",s);
if (!strcmp("b",argv[1]))
if ((!strcmp(s,"33.1.6.3.3"))||(!strcmp(s,"upsMIB.upsObjects.upsAlarm.upsWellKnownAlarms.upsAlarmLowBattery")))
powerfail(1);
if (!strcmp("p",argv[1]))
if ((!strcmp(s,"33.1.6.3.3"))||(!strcmp(s,"upsMIB.upsObjects.upsAlarm.upsWellKnownAlarms.upsAlarmLowBattery")))
powerfail(0);
}
/* このプログラムは警報の場合にも起動されるので、内側の 'if' が必要 */
void powerfail(int event) {
int fd;
unlink(PWRSTAT);
if ((fd = open(PWRSTAT, O_CREAT|O_WRONLY, 0644)) >= 0) {
switch (event)
{
case 0:
write(fd, "OKWAIT\n", 7);
break;
case 1:
write(fd, "FAIL\n", 5);
break;
}
close(fd);
}
kill(1, SIGPWR);
}
3. 自分のシステム上で snmptrapd を走らせる(これはinitスクリプトコンフィギュアすることが出来る)
システムは、UPSから「バッテリ電圧低下警報」を受けた二分後に停止する。次いで、停止二分前に電源が良くなると、停止を中止するか、又は/etc/inittabのpowerfail 及び powerokwait 行でコンフィギュアした通りにする。
コードの説明
trap 33.2.3 (upsMIB.upsTraps.upsTrapAlarmEntryAdded) を受け取ったとき、プログラムは、'b' オプションで実行される。プログラムはtrapの送る 'upsAlarmId' をチェックし 33.1.6.3.3 (upsMIB.upsObjects.upsAlarm.upsWellKnownAlarms.upsAlarmLowBattery)であれば、initに 電源不具合発生を通知する。バッテリの残留稼働時間がupsConfigLowBattTime以下であるとこれをUPSエージェントがアラームテーブルに追加する。これは電源が元に戻ったことをtrap 33.2.4.が認識したとき削除される。プログラムは次いで init に powerokwait を送る。
欠点
あらゆる種類のご意見歓迎 mailto://prasad_ab@yahoo.com へどうぞ。私の home page も見られたい。
print "abcdef" =~ /de/,"\n" print "aaaaaa" =~ /d/,"\n" ^D 3 FALSE
演算子 `=~' は規則的表現に関する整合演算子である。これは、整合の見出された文字列ないの位置を返すか、又はパターンが一致しないときは空白を返す。規則的表現が下に示すように特定種類の語彙を共有するのが分かるのは面白い:
[ ] 範囲規定。(例えば, [a-z] はaからzの範囲の文字を意味する)
\w 文字又は数字。[0-9A-Za-z_]と同じ。
\W 文字でも数字でもない。
\s 空白文字。[ \t\n\r\f]と同じ。
\S 空白でない文字
\d 数字。[0-9]と同じ。
\D 数字でない文字。
\b 単語の境界 (範囲規定の外側)。
\B 単語の境界でない。
\b 後退 (0x08) (範囲規定の内側)
* 次に続く表現のゼロ回以上の繰り返し
+ 次に続く表現の1回以上の繰り返し
{m,n} 次に続く表現の最低n回で、m回以上ではない繰り返し
? 次に続く表現の最低0回で、1回以上ではない繰り返し
| 前又は後の表現のいずれかが一致する
( ) グループ分け
例えば、'^f[a-z]+' は、「'a'から'z'までの範囲の文字表現に続くf」を意味する。ここで、文字列が示された説明例えば「正確に一つの大文字に続く小文字'f'で始まり、その後に続く小文字がなくなるまで」の文字列を点検したいとしよう。Cでは、数十行を書かなければならない筈だ。面倒だ。Rubyでは、規則的表現/^f[A-Z](^[a-z])*$/との一致を文字列に要求するだけで済む。文字列における規則的表現のこの能力は、Unix環境で使われることが多い。典型的な例は `grep' だ。規則的表現に慣れて見よう。次の例を考える:
#これを regx.rb として記憶する
st = "\033[7m"
en = "\033[m"
while TRUE
print "str> "
STDOUT.flush
str = gets
break if not str
str.chop!
print "pat> "
STDOUT.flush
re = gets
break if not re
re.chop!
str.gsub! re, "#{st}\\&#{en}"
print str, "\n"
end
print "\n"
#ここで ruby regx.rb を走らせる。
このプログラムは、二度の入力を必要とする。文字列一度と規則的表現一度だ。テストは規則的表現に対する文字列につき実行し、一致した部分をリバースビデオで強調する。これは、リバースビデオ・エスケープ・シーケンスを使うので、ANSIターミナルを必要とする。プログラムの詳細を気ににないこと。
str>foobar pat>^fo+ foobar ~~~
foo が逆転しているのが分かる。 Note that ``~~~'' は、テキストベースのブラウザーのために過ぎないことに注意。違う入力に付いて実験する。
str>asd987wonew06521 pat>\d asd987wonew06521 ~~~ ~~~~~ str>foozboozer pat>f.*z foozboozer ~~~~~~~~
foozbooz は一致して fooz はそうでないことに注意。これは、規則的表現が可能な最も長い文字列 と一致させるためだ。一瞥して解釈は難しい。次を試そう:
str> Wed Feb 7 08:58:04 JST 1996
pat> [0-9]+:[0-9]+(:[0-9]+)?
Wed Feb 7 08:58:04 JST 1996
~~~~~~~~
ここで、規則的表現を使って16進数を表して見る(0x123af00c や 0Xbc13590aeは16進数だ)。
def chab(s) # "括弧内に16進数を含む"
(s =~ /<0(x|X)(\d|[a-f]|[A-F])+>/) != nil
end
print chab "Not this one."
print "\n",chab "Maybe this? {0x35}" # 間違った種類の括弧の使用
print "\n",chab "Or this? <0x38z7e>" # これは HEX 数であるか
print "\n",chab "Okay, this: <0xfc0004>."
print "\n"
^D
false
false
false
true
イテレータ
イテレータとは「同じことを何度もおこなうもの」を意味する。次のCコードを考える:
char *str;
for (str = "abcdefg"; *str != '\0'; str++) {
/* process a character here */
}
ループを作るためのCの for(...) 構文が与える抽象概念には留意するが、事実上、プログラマはnull文字の付いた *str をテストするには文字列の内部構造を知らなければならない。次のシェルスクリプト (/bin/sh) を考える:
for i in *.[ch]; do # ... ファイル毎になすべきこと done
カレント・ディレクトリの中のCソース及びヘッダファイル全部が処理され、コマンドシェルは、ファイル名の獲得と置換の詳細を取り扱う。これはCより高いレベルで働いていないだろうか?君はどう考えるか?
埋込データ型のためのプログラム言語にイテレータを備えるのは、好ましいとの事実を考えながらも、我々のデータ型を繰り返すため低レベルループを書かなければならないとすると失望する。00Pにおいて、これは容易でない問題となる。ユーザはデータ型を一つ宛次々に決めるからだ。
上の問題を解くには、各00P言語は繰り返しを容易にする方法を磨き上げなければならない。例えば、若干の言語ではクラスを制御する繰り返しを備えている、などである。他方、rubyによると制御構造を直接定義することが出来る。rubyの用語では、このようなユーザ定義制御構造をイテレータと呼ぶ。
数個の例を示す:
"abc".each_byte{|c| printf "%c\n", c}
^D
a
b
c
ここでは、each_byte が文字列中の各文字に関するイテレータである。ここではローカル変数 'c' が用いられ、各文字がそれに代入される。これはCコードに良く似たものに解釈することが出来る...
s="abc" i=0 while i < s.length printf "%c\n",s[i] i+=1 end ^D a b c
... しかし、each_byte イテレータの方が概念的に簡単で、ストリングクラスが将来激しく修正されることがあっても、引き続き働くと思われる。このイテレータの一つの利点は、このような変更に際して堅牢であることで、これは良いコードの特性であると考える。
文字列の別のイテレータは each_line である。
"a\nb\nc\n".each_line{|l| print l}
^D
a
b
c
行に関するデリミタの発見、補足文字列の発生などのような退屈な作業のそれぞれをイテレータが取り上げる。
forステートメントを使ってこの例を書いて見る。
for l in "a\nb\nc\n"
print l
end
^D
a
b
c
for ステートメントは、各イテレータを用いて繰り返しをおこなう。文字列はそれぞれ前の例で見た each_line と同じように働く。
今の繰り返しは、一つの繰り返しループとの関連で制御構造体 'retry' を用いて最初からやり直すことが出来る。下記を参照:
c = 0
for i in 0..4
print i
if i==2 and c==0
c = 1
print "\n"
retry
end
end
^D
012
01234
イテレータの定義には、`yield' が発生することがある。これは、イテレータに渡されたコードのブロックに制御を移動する(後で詳細が分かる)。下記の例は、イテレータ繰り返しを定義する。これは引数で規定された回数だけコードのブロックを繰り返す。
def repeat(num)
while num < num
yield
num-=1
end
end
repeat(4) {print "hello world\n"}
^D
hello world
hello world
hello world
これでハッキリしなければ、`yield' が生じる前と後にnumの値をプリントしてみる。
`retry' を用いて、`while' と同じ働きのイテレータを定義することが出来るが、遅いので実用的ではない。
def MYWHILE(cond)
return if not cond
yield
retry
end
i = 0
MYWHILE(i<3) {print i,"\n" ;i+=1}
^D
0
1
2
ここまでで、イテレータの考えがほとんど分かった筈だ。少し制限があるものの、自分で独自のイテレータを書くことが出来る筈で、事実、新しいデータ型を定義するときはいつでも、それに伴う適切なイテレータを定義するのが便利だ。その意味では、上例の `repeat() と `MYWHILE()' は余り有用ではない。クラスを良く理解した後に、実用的なイテレータを説明する。
オブジェクト指向の考え方
`オブジェクト指向' は全く覚えやすい熟語だ。Ruby はオブジェクト指向スクリプト言語であると言うが、「オブジェクト指向」の正確な意味は何だろう?
この疑問には色々な回答がある。それらすべてが、多分同じことに煮詰まるだろう。慌てて議論をまとめる前に、伝統的プログラム体制を一寸考えて見よう。
伝統的に、プログラム作成上の問題は、ある種のデータ表現、及びそのデータに作用する手順に伴って浮かび上がることにより取り組まれて来た。このモデルの下の「データ」には不活性、受動的、及び孤立無援の用語を結合することが出来るが、そのデータは完全に、活性、論理的、及び強力の用語が結合される大きい手順上の本体のなすがままであった。
この方法に伴う問題は、人間に過ぎなくて頭の中に一時に沢山の内容を明確に保つだけのプログラマがプログラムを書くことである。プロジェクトが大きくなるにつれ、手順の核は、全体の働きを覚えるのが困難な点に成長する。考えの一寸した間違いと印刷上の過ちが、全く隠されたバグになることがある。複雑で予期しない相互作用が手順上の核の中に現れはじめ、不気味に活動する。この伝統的領域の中でバグを最少にし見つけ出すのを助けることの出来るプログラム作成用指針があるが、作業の方法を根本的に変える良策がある。
オブジェクト指向がおこなうことは、平凡な繰り返し論理作業をデータ自体に委せることである。データの概念を受動から能動に変えることが出来る、言い換えると、
上に「機械」と述べたものは、内部が極めて簡単なことも複雑なこともある。外からはわからないので、機械を自分で開くことはしない(その設計に何かの間違いがあると確信があるときを除く)、だから、データに働き掛けるためすることは、スイッチを入れてダイアルを読むのに似ている。一旦機械が構築されると、その作用方法は考えない。
沢山の作業をしようとしていると思われるだろうが、この方法は、あらゆる種類の事柄が間違った方向に進むのを防止する。
実用価値には簡単過ぎるが、少なくとも考え方の一部を示す例から始める。私の二輪車には距離計が付いている。その役割はリセットボタンを押してからの走行距離を追跡することだ。これをプログラム言語ではどのようなモデルにするだろうか?Cでは、距離計は、多分浮動小数点の只の数値変数である。プログラムはこの変数を、適当な時期にゼロにリセットしながら、小さい増分で増やして操作する。間違いがあるだろうか?このプログラムのバグは、予期しない色々な理由で変数に偽の値を割り当てる。Cでプログラムしたことのある人は知っているように、見付けて見れば簡単に思われる原因のバグを追跡するのに数時間も数日も掛かることがある。(バグを見付けた瞬間に大方の人は、額を叩く)
オブジェクト指向の文脈では、同じ問題に別の方法で取り組む。距離計を設計するプログラマは「慣れたデータ型のうちどれがこれに最も近いくなるか」とは聞かないで、「これは正確にどんな作用とするか?」と聞く思われる。この相違は意味深なものとなる。走行距離計が何で、外界はそれとどんな相互作用をするか、一寸考えてみよう。我々は、刻みで増加し、リセットし、その値を読み取ることが出来る他には何もしない制御を有する小機械を構築すると決める。
我々は、距離計に勝手な値を割り当てる方法は作らない。距離計はそんな風の働きをしないからだ。距離計に対して出来ることは限られており、それら全部は分かっている。だから、プログラム中の何か別のものが何か別の値(車両温度制御の目標値など)を間違って距離計に入れようとしたら、何を間違ったか即座に表示する。プログラムを走らせているとき(言語によっては多分コンパイルのとき)距離計オブジェクトに勝手な値を割り当てることは許されていないことを通告される。メッセージはこれ程明確でないかも知れないが、それに近い筈だ。これはエラーを防止しない。だが、原因の方向を迅速に指し示す。これは00プログラム作成が無駄な時間を節約する幾つかの方法の一つに過ぎない。
これを越える抽象化の一歩を踏み出す。多くの機械を作る工場を建てるのは、個別の機械を作るのと同じように容易だからだ。単一の距離計を直接作ることはない、むしろ、任意の数の距離計を単一のパターンで作る手配をする。そのパターン(お好みなら距離計工場)は、クラスと呼ぶものに相当し、工場はオブジェクトに相当する。ほとんどの00言語は、新しい種類のオブジェクトを持つことが出来る前にクラスが定義されることを必要とする。だがrubyでは必要がない。
00言語の使用は、正しい00設計を強化しないとの事実を強調したい。実際、どの言語においても、不明確で、ずさんで、計画の悪い、バグだらけで、ガタガタのコードを書くことが出来る。rubyに出来ることは(特にC++に対して)、00プログラム作成の実用を充分自然な感じにするので、小規模の作業をするときでも、労力を節約するため醜いコードの助けを借りる必要を感じない。この案内書が進むにつれて、rubyがこの好ましい目的を達する方法を論じる。次の話題は、「スイッチとダイアル」(オブジェクト・メソッド)で、そこから「工場」(クラス)に移る。ご一緒なさいますか?
注記: 混乱しないで頂きたい。これはptraceに関する記事であって、ELFの記事ではない。だが、ELFに関する基本知識はプロセスのコアイメージに近づくのに必要である。だから、それを先ず説明しなければならない。
1.ELFとは?
ELFはExecutable and Linking Format(実行可能リンクフォーマット)の略である。これはLinuxで使用される実行可能バイナリのフォーマットを定義し−リロケータブル、共有オブジェクト及びコアダンプファイルのフォーマットもまた定義する。ELFはリンカーとローダーの両方で使用する。これらは、ELFを両側から見るので、双方とも共通のインターフェイスを持たなければならない。
ELFは、多くのセクションとセグメントを持つ構造となっている。リロケータブル・ファイルはセクション・ヘッダ・テーブルを、実行可能ファイルはプログラム・ヘッダ・テーブルを、共有オブジェクトファイルは両方を持っている。これらヘッダについては後の節で説明する。
2.ELFヘッダ
すべてのELFファイルはELFヘッダを有する。これはファイル中で常にオフセット0から始まる。これにはバイナリファイルの詳細が含まれ−ファイルに関連するデータ構造などに、翻訳される。
ヘッダのフォーマットはを下に示す( /usr/src/include/linux/elf.hから採用)
#define EI_NIDENT 16typedef struct elf32_hdr{unsigned char e_ident[EI_NIDENT];Elf32_Half e_type;Elf32_Half e_machine;Elf32_Word e_version;Elf32_Addr e_entry; /* Entry point */Elf32_Off e_phoff;Elf32_Off e_shoff;Elf32_Word e_flags;Elf32_Half e_ehsize;Elf32_Half e_phentsize;Elf32_Half e_phnum;Elf32_Half e_shentsize;Elf32_Half e_shnum;Elf32_Half e_shstrndx;} Elf32_Ehdr;
ファイルに関する説明は次の通り
1.e_ident : バイナリの扱い方に関する情報を含む。プラットホームに左右される。
2.e_type : バイナリのタイプ及び使い方に関する情報を含む。タイプはリロケータブル、実行可能、共有オブジェクト及びコアファイル。
3.e_machine : 推測通り、このフィールドは、アーキテクチャ−Intel 386, Alpha, Sparc など−を規定する。
4.e_version : オブジェクト・ファイルのバージョンを示す。
5.e_phoff : スタートから最初のプログラム・ヘッダまでのオフセット。
6.e_shoff : スタートから最初のセクション・ヘッダまでのオフセット。
7.e_flags : プロセッサ固有のフラッグ、i386 では使用しない。
8.e_ehsize : ELF ヘッダのサイズ。
9.e_phentsize & e_shentsize : それぞれプログラム・ヘッダとセクション・ヘッダのサイズ。
10.e_phnum & e_shnum : プログラム・ヘッダとセクション・ヘッダの数。プログラム・ヘッダ・テーブルはプログラム・ヘッダのアレーとなる(e_phnum 要素)。セクション・ヘッダ・テーブルの場合も同様。
11.e_shstrndx : セクション・ヘッダ・テーブルには、セクション名が含まれる。これはテーブル中のそのセクションに対するインデキス(下記を参照)
3.セクションとセグメント
上述のように、リンカーはファイルを、セクション・ヘッダ・テーブルが記述する論理セクションのセットとして取り扱い、ローダーはファイルを、プログラム・ヘッダ・テーブルが記述するセグメントの組として取り扱う。以下の節で、セクションとセグメント/プログラム・ヘッダの詳細を示す。
3.1 ELFセクションとセクション・ヘッダ
バイナリ・ファイルは、バイトが二重になっていないバイトのアレーであるセクションのコレクションであると見なされる。セクション内容を正しく解釈するためヘッダ情報があるけれども、アプリケーションはそれを自分の方法で解釈する。
セクション・ヘッダのアレーであるセクション・ヘッダ・テーブルがある。テーブルの0番登録は常にNULLでバイナリのどの部分も記述しない。各セクション・ヘッダは次のフォーマットを有する:(/usr/src/include/linux/elf.hから採用)
typedef struct elf32_shdr {Elf32_Word sh_name; /* セクション名, 文字列テーブルのインデキス (yes Elf32) */Elf32_Word sh_type; /* セクションのタイプ (yes Elf32) */Elf32_Word sh_flags; /* セクションの雑属性 */Elf32_Addr sh_addr; /* 実行時のセクション仮想アドレス */Elf32_Off sh_offset; /* セクション・ファイル・オフセット */Elf32_Word sh_size; /* セクションのバイト・サイズ */Elf32_Word sh_link; /* 別のセクションのインデキス (yes Elf32) */Elf32_Word sh_info; /* 追加セクション情報 (yes Elf32) */Elf32_Word sh_addralign; /* セクション・アライメント */Elf32_Word sh_entsize; /* セクションがテーブルを保持するとき登録数 */} Elf32_Shdr;
ファイルの詳細を下記。
1.sh_name : これは e_shstrndx 文字列テーブルのセクション内容へのインデキスを含む。このインデキスは、セクション名として使われる null 終了文字列のスタートである。沢山あるが、数個を示す。
2.sh_type : プログラム・データ、記号テーブル、文字列テーブルなどのセクションタイプ。
3.sh_flags : セクション内容の取扱い法などの情報を含む。
4.sh_addralign : セクション内容のアライメント要件を含む、一般的に 0/1(両方ともアライメント無し)又は4である。
残りのフールドは自明であると思われる。
3.2 ELFセグメントとプログラムヘッダ
ELFセグメントはロード中、つまりプロセスのイメージが
The ELF segments are used during loading ie, when the image of the process is made in the core. Each segment is described by a program header. There will be a program header table in the file (usually near the ELF header). The table is an array of program headers. The format of the program header is as follows.
typedef struct{Elf32_Word p_type; /* セグメント・タイプ */Elf32_Off p_offset; /* セグメント・ファイル・オフセット */Elf32_Addr p_vaddr; /* セグメント仮想アドレス */Elf32_Addr p_paddr; /* セグメント物理アドレス */Elf32_Word p_filesz; /* ファイルで表したセグメント・サイズ */Elf32_Word p_memsz; /* メモリで表したセグメント・サイズ */Elf32_Word p_flags; /* セグメント・フラッグ */Elf32_Word p_align; /* セグメント・アライメント */} Elf32_Phdr;
1.p_type : 内容の取扱い法に関する情報を示す。次のようにプログラム・ヘッダのタイプを示す
など..
2.p_vaddr : セグメントがロードされると予想される相対仮想アドレス
3.p_paddr : セグメントがメモり内にロードされると予想される物理アドレス
4.p_flags : 保護フラッグを含む - 読取/書込/実行許可
5.p_align : メモリ中のセグメントに関するアライメントを含む。セグメントのタイプがローダブルであると、アライメントは予想ペイジサイズとなる。
残りのファイルは自明であると思われる。
4.ELFファイルのロード
ELFオブジェクト・ファイルの考え方が少し分かった。ここで、これらのファイルを実行のためロードする方法と場所を知らなければならない。通常は、シェルプロンプトでプログラム名をタイプするだけだ。実際は、リターンキイを押した後、興味あることが沢山起こる。
先ず、シェルが標準libcファンクションを呼び出し、これが次いでKernelルーチンを呼び出す。ここでボールはkernelに渡される。kernelがファイルを開き実行可能ファイルのタイプ/フォーマットを見出す。次いでELFと必要ライブラリをロードし、プログラムのスタックを初期化し、最後に制御をプログラムコードに渡す。
プログラムは、0x08048000 (これは /proc/pid/maps に見出すことが出来る)にロードされ、スタックは0xBFFFFFFF (スタックは数値的に小さいアドレスから伸びる)から始まる。
5.コード注入
プログラムがメモりにロードされる詳細を見た。そこで、プログラムが与えられ、そのメモリ空間が分かったとき、(パーミッションを持っていれば)それを追跡してプロセスのプライベートデータ構造体にアクセスすることが出来る。これは、言うは易いが、為すのは難い。やってみよう。
先ず最初に、別のプログラムのレジスタにアクセスして、それを変更するプログラムを書く。ここで要求に関する次の値を使用する。
注記 : これを呼び出すのを忘れないこと。でないと、プロセスは停止モードのままに止まり復旧が難しくなる。
struct user_regs_struct と定義された構造体である。
struct us