2012年10月25日

Python ScriptをLinuxコマンドとしてinstallする「installer.py」

 前稿でのuunzip公開でようやく、文字化けから開放されたZIP解凍を提供出来たはずではあるが、このままでは、やはり、ある程度プログラミングに馴染みがあるか興味のある方しか利用しようとしないだろう。折角、作ったからには、それなりに、その便益を広く共有したいと言うのが人情だ。その為には、helpにある通り、「python uunzip.py」ではなく「uunzip」のみで利用出来る様にするinstallerを付けたパッケージの作成が、不可欠である。
 本稿は、その実現の為に今回作成したinstallerを公開することにする。仕様としては次の様になっている。
  1.  「拡張子を省いて実行出来る様にinstallしたいPythonスクリプト」自体を走らせたいPythonへのフルパスで、この「instaler.py」を実行することで、拡張子を省いて実行出来る様にinstallしたいPythonスクリプトの実行Pythonを決定する。
  2.  第1引数には、拡張子を省いて実行出来る様にinstallしたいPythonスクリプトのコードを指定する。この「instaler.py」は、そのソースコードを読込み、第1行目にある実行Pythonへのパスを、1の指定通りに書替えたものを「/usr/local/bin」(既定)以下に拡張子の「.py」を外したファイルで書込み、必要となる実行権付与等を行なう様にしている。
  3.  必要なら、第2引数に指定することで、既定のinstall先「/usr/local/bin」を別のものに変更することも出来る。但し、そのinstall先は、環境変数PATHに予め登録されていることが必要である。もし、環境変数PATHにないパスを指定したいなら、その登録は、手作業で行なって頂くことが前提なので、既定の「/usr/local/bin」でないなら、個人のみに有効な「~/bin」辺りを推奨する。でも、ここに実際には無いディレクトリを指定すると、その旨表示して終了する。これも、事前に別途、手動で作成することが必要と言うことだ。
  4.  install処理が終わったら、その実体を検査表示する様にした。
  5.  使い方は、「instaler.py」に引数を何も付けずに実行することで表示する。
  6.  当然ながら、「/usr/local/bin」等ユーザのホーム領域以外をinstall先とするには管理者権限で実行することが必須となるので、予め、rootパスワードを使って「su -」で、管理者になって行なうか、ユーザへの設定を予めした上で「sudo」とユーザパスワードで、行なうことになる。

 「uunzip」での具体的な実行例は次の通りである。実行させるPythonの指定は、必ずしも、この様にバージョン番号付である必要はないが、明示しておくことを推奨する。例えば、CentOS6の素の環境では/usr/bin配下に2つ程、Pythonの実行ファイルがあるが、これらはいずれも同じものであるが、これがOSのupdateで勝手にグレードアップまたはダウンされたとしたら、動作しなくなる可能性は完全否定することは出来ないからである。無論、当方の公開しているPythonプログラムであれば、アクロバティックなものはないので、Python2.6が、2.5に落ちようととも、2.7に上がろうとも、外部モジュールに問題がなければ動作する訳で、その傾向はPython2.xの互換性重視の姿勢によるものだ。例えそうであっても、手持ちのものがどの環境で動作することを前提に存在したかを明示する価値はあるものと思うからである。




[mire@localhost ~]$ ls -la /usr/bin/python* | grep -v config
-rwxr-xr-x. 2 root root 9032 6月 18 22:19 2012 /usr/bin/python
lrwxrwxrwx. 1 root root 6 6月 19 02:12 2012 /usr/bin/python2 -> python
-rwxr-xr-x. 2 root root 9032 6月 18 22:19 2012 /usr/bin/python2.6
[mire@localhost ~]$
[mire@localhost ~]$ sudo /usr/bin/python2.6 installer.py uunzip.py
[sudo] password for mire:
This installer runs with '/usr/bin/python2.6'
set 'uunzip.py' to /usr/bin/python2.6
'/usr/local/bin/uunzip' was wroted.

Check result. See below.
# ls -l /usr/local/bin/uunzip
-r-xr-xr-x. 1 root root 68312 10月 25 01:39 2012 /usr/local/bin/uunzip

with executing permission
# printenv | grep PATH
PATH=/sbin:/bin:/usr/sbin:/usr/bin

/usr/local/bin already exists in PATH.
# whereis uunzip
uunzip: /usr/local/bin/uunzip

'uunzip' command exists.
[mire@localhost ~]$


 以下が、「installer.py」のソースコードである。今回、当方では「uunzip」の配布用に活用するが、同様の用途であれば、そのままでも充分に活用出来るだろう。ライセンスは、例により、GPLなので、ソースコードも含めて有効に活用頂ければ有難い。

            installer.py


#-*- coding: utf-8 -*-
u"""Installer for Python Script as Linux Command
PythonプログラムをLinux上で、一般のアプリ的な
コマンドとして利用可能な様にするインストーラー"""

__author__ = "Mire in Japan"
__date__ = "2012-10-24"
__version__ = '0.0.2'
__copyright__ = 'Copyright (c) 2012-10-15 Mire'
__license__ = 'GPL'
__url__ = 'http://pythonlife.seesaa.net/ '

__history__ = u"""【更新履歴】
0.0.1 2012-10-15 初版
0.0.2 2012-10-24  ls(), printenvpath(), whereis()関数を作りinstaller()実行後の
チェック表示を加えた。
"""

usage = u"""
Usage: <python実体> installer.py <対象のpythonスクリプト>[ <install先>]
example: sudo /usr/local/bin/python2.6 installer.py app.py /usr/local/bin

1.  このプログラムは、例えばyum, autoconf, alacarteの様に、実行可能なPython
スクリプトを、見た目上、拡張子が省略されたLinuxコマンドとして、利用可能な
様にinstallする為のものです。

2.  このプログラムは、既定通りに/usr/local/bin以下への書込みを行なう場合には、
管理者(root)権限での実行が必要となります。これは第2引数で変更可能です。OSの
環境変数PATHにないinstall先なら登録すれば機能しますが、全ユーザー向けなら
/usr/local/binに個別ユーザなら、ユーザーのbinにinstallするのが一般的です。
環境変数PATHは「printenv | grep PATH」で、複数回入れ何処のものが動作するかは
whereisで有効なパスを調べ、環境変数PATHの順位で判断して下さい。install後の
パス確認は「whereis unzip」で確認出来ますが、何も表示されない様なら、先ずは
「ls -la <install先>」として、unzipファイルの存在とその実行権限の確認、そして
「printenv | grep PATH」に<install先>が含まれるかどうか確認して下さい。通常、
上手くいかない要因としては<install先>のタイプミス、管理者(root)権限辺りです。
 このinstaller.pyではinstall完了後にこれらのコマンドを自動実行しますので、
そちらで確認頂くと宜しいでしょう。

3.  実行時には、Pythonスクリプトを実行させるPythonへのPathを指定、且つ見た目の
Linuxコマンド化したいPythonスクリプトを引数として指定します。
 但し、そのPythonスクリプトの1行目には実行に使うPythonの指定記述が必要です。
その記述は他からの移植で環境不適合のままで構いません。最低、ヘッダーの'#!'と
言語名の'python'の記述があれば、多分動作します。
"""

def select(prompt='answer? [Y]es [N]o: ' #プロンプト表示文字列
, answers=['Y', 'N'] #選択肢
, upper=False): #大文字で比較
u"""
【処理方法の確認入力用の汎用関数】
コンソールにプロンプトを出し、求める範囲の返答の英字1文字を返す関数
"""
from sys import stdin #コマンドラインからの標準入力
print prompt, #プロンプトを表示し、処理確認
ans = stdin.readline()[0] #処理方法確認の頭1文字取得し
if upper: #新設upper引数が真なら
ans = ans.upper() # 大文字変換し
while not ans in answers: #リストの選択肢以外なら、
print prompt, # プロンプトを表示し
ans = stdin.readline()[0] # 処理方法の確認を繰り返す
if upper: # 新設upper引数が真なら
ans = ans.upper() # 大文字変換し
return ans #求める範囲と一致した文字1つを返す

def ispyscript(file, debug=0):
u"""
pythonスクリプトか否かを形式判定する関数
(1行目の明示的起動言語指定記述のみで判定)
"""
from re import compile, match, search
fpi = open(file) #ファイルを開き
s = fpi.readline() #1行読んで
fpi.close() #ファイルを閉じる
ptrn = compile('^(\w)*(#!)+[a-z0-9/]*(python)+[0-9\.]*$') #正規表現かぁ、ん〜慣れんなぁ。
ptrn = compile('^(\w)*#![a-z0-9/]*python[0-9\.]*$') #正規表現かぁ、ん〜慣れんなぁ。
# # '^(\w)*' 行頭には0文字以上の空白文字は許すが
# # '#!' #!が一回だけ必ずあり、
# # '[a-z0-9/]*' 半角英数と/を0個以上含むことを許し
# # 'python' pythonが一回だけ必ずあり、
# # '[0-9\.]*$' 行末には数字と'.'のみを0個以上含むことを許す
# debug = 11
# if debug>10: #ということで、蛇足のBug check
# print '1st line:', s, # 1行目を表示
# m = search(ptrn, s) # 合ってるとは思うがちゃんとマッチするのか
# if not m==None: # チェックし、
# print 'matching:', m.group() # マッチ全体を表示
# print s[m.start():m.end()] # ついでにメソッドテスト
# else: # マッチしないなら、その旨表示
# print 'matching: None (This file\'s 1st line was not matched this pattern.)'

if match(ptrn, s): #pythonスクリプトのパターンなら、
return True # 真を返す
else: #でないなら、
return False # 偽を返す

def install(target_source, filepath2install, header_line, mode=0555):
u"""ターゲットのPythonスクリプトを実行ファイルとして
指定場所へ書き込み権限を含めた処理を行なう本体関数"""
from os import chmod
#ヘッダ行を置替えてファイル書込
print u'\'%s\' is writting ' % (filepath2install),
fpi = open(target_source, 'r') #Pythonスクリプトファイルを読込モードで開く
fpo = open(filepath2install, 'w') #コマンドとしてのファイルを書込モードで開く
line = fpi.readline() #1行読み飛ばす
fpo.write(header_line) #代わりに指定されたPython本体へ置替えて書込
while line: #以降、行がある限り
line = fpi.readline() # 行を読み取り
fpo.write(line) # 追記して行く
fpo.close() #コマンドとしてのファイルを閉じる
fpi.close() #Pythonスクリプトファイルを閉じる
print '\r\'%s\' was wroted. ' % (filepath2install)
chmod(filepath2install, mode) #ファイルに実行権限を付与

def installer(target_source, path2install='/usr/local/bin', mode=0555):
u"""
既存の同名ファイルの上書き確認処理を行なう関数
"""
from sys import argv
from sys import version, version_info, executable, stdin
#実行指定したPython情報等の取得用
from os.path import exists, join, splitext #ファイル存在の判定用

if version_info[0]==2: #Pythonの主バージョンが2なら、
print 'This installer runs with \'%s\'' % (executable)
# Python実体位置を表示
print u'set \'%s\' to %s' % (argv[1], executable)
# 指定Pythonプログラムをその実体に設定します。
filepath2install = join(path2install # 拡張子を外したファイル名
, splitext(target_source)[0])
header_line = u'#!' + executable + '\n'
if exists(filepath2install): # がinstall先にあるなら
print u'\n\'%s\' already exists!' % (filepath2install)
ls(path2install, target_source=argv[1])
prompt = u'replace \r\'%s\'? [Y]es, [N]o: ' % (filepath2install)
# 置替え確認の問合せ文字列
answers = ['Y', 'N'] # 問合せ選択肢
ans = select(prompt=prompt, answers=answers, upper=True)
# 上書き可否の問合せ
if ans.upper() == 'Y': # Y, y なら
install(target_source=target_source
, filepath2install=filepath2install, header_line=header_line
, mode=mode) # そのまま上書きでinstall処理
else: # N, n なら
return # 処理なく抜ける
else: # そうでなく上書きとはならないなら、
install(target_source=target_source # install処理
, filepath2install=filepath2install
, header_line=header_line, mode=mode)
else: #Pythonの主バージョンがそれ以外なら、
print u'Sorry, this system work with python2.x.'
# これがPython2.x用であると素直に謝る
print u'Yours version is %s' % (version)# 起動しているPythonのバージョンを表示

def ls(dir,target_source, ls = 'ls -l', msg=False):
u"""
指定ディレクトリ内のコマンドファイルの詳細
リストを表示し、その実行権をチェックする関数
"""
from os import popen #OSコマンドの実行用
from os.path import join, splitext #pathの接続と拡張子分割用
cmd = '%s %s' % (ls, join(dir, splitext(target_source)[0]))
print '#', cmd #実行コマンド文字列の生成と表示
run = popen(cmd, 'r') #OSコマンドの実行
lines = run.readlines() #実行時の標準出力行リストの取得
s = ''.join(lines) #それを文字列として結合
print s #それを表示
if s[3]==s[6]==s[9]=='x': #ユーザ、グループ、その他の実行権が全てあるなら、
if msg: # メッセージ表示でいいなら、
print 'with executing permission'
# 実行権付きの表示をしてから
return True # 真を返す
else: #無いなら、
if msg: # メッセージ表示でいいなら、
print 'without executing permission'
# 実行権無しの表示をしてから
return False # 偽を返す

def printenvpath(dir, cmd='printenv | grep PATH', msg=False):
u"""
'printenv | grep PATH' を実行し、指定ディレクトリが
PATHにあるかどうかをチェックする関数
"""
from os import popen #OSコマンドの実行用
print '#', cmd #実行コマンド文字列の生成と表示
run = popen(cmd ,'r') #OSコマンドの実行
lines = run.readlines() #実行時の標準出力行リストの取得
s = ''.join(lines) #それを文字列として結合
print s #それを表示
if s.find(dir): #その中に、指定ディレクトリがあれば、
if msg: # メッセージ表示でいいなら、
print '%s already exists in PATH.' % (dir)
# 既にある旨表示し
return True # 真を返す
else: #無いなら、
if msg: # メッセージ表示でいいなら、
print '%s not exists in PATH. Set it manualy, if you wish.' % (dir)
# 無い事とと所望なら手動設定となる旨表示し、
return False # 偽を返す

def whereis(command_name, msg=False):
u"""
whereis を指定コマンドに対し実行し、PATHの通りをチェックする関数
"""
from os import popen #OSコマンドの実行用
from re import compile, findall #コマンド名文字列の出現回数調査用
cmd = 'whereis %s' % (command_name) #実行コマンド文字列の生成
print '#', cmd #と表示
run = popen(cmd ,'r') #OSコマンドの実行
lines = run.readlines() #実行時の標準出力行リストの取得
s = ''.join(lines) #それを文字列として結合
print s #それを表示
pat = compile(command_name) #比較パターンの生成
n = len(findall(pat, s)) #パターン一致文字列出現数をnに
if n<=1: #1回以下なら、
if msg: # メッセージ表示でいいなら、
print '\'%s\' command does not exist.' % (command_name)
# このコマンドは無い旨表示
return None # 空だと返す
elif n==2: #2回なら、
if msg: # メッセージ表示でいいなら、
print '\'%s\' command exists.' % (command_name)
# このコマンドが在る旨表示
elif n>2: #2回より多いなら、
if msg: # メッセージ表示でいいなら、
print 'There are \'%s\' two or more. Check which is executed.' % (
command_name) # 2つ以上の記述の存在が要調査の旨表示
return n #出現数を返す。

def main(usage):
from sys import argv, executable
from os.path import exists, splitext

path2install = '/usr/local/bin' #コマンドとしてのistall先既定
mode = 0555 #全てに対し読込と実行権を付与

if len(argv)>=3:
path2install = argv[2] #コマンドとしてのistall先指定

if len(argv)>=2: #引数が1つ以上あって、
if exists(argv[1]): # 引数文字列のファイルで存在し、
if ispyscript(file=argv[1]): # それがPythonスクリプトなら、
if len(argv)>=3: # 引数が2つ以上あるなら、
path2install=argv[2] # 引数をinstallディレクトリに変更し
if not exists(path2install): # そのディレクトリが存在しないなら、
print u'\'%s\' does not exists. Make this directory, \
if you wish. ' % (path2install) # その旨表示して
return # 処理を飛ばして戻り終了へ
installer(target_source=argv[1], path2install=path2install
, mode=mode) # それらの値でインストーラを実行
print u'\nCheck result. See below.' # 結果チェックの為、以下を参照と表示
ls(path2install, target_source=argv[1]
, msg=True) # ls -l <istall先> を実行
printenvpath(path2install, msg=True)# printenv | grep PATH を実行
whereis(splitext(argv[1])[0], msg=True)
# whereis <コマンド名> を実行
else: # Pythonスクリプトでないなら、
print u'This file is not python script.'
# その旨表示
else: # 引数文字列のファイルが無いなら、
print u'There is not \'%s\'.' % (argv[1])
# その旨表示
else: #引数指定がないなら、以下のヘルプ表示
print u'Installer for Python Script as Linux Command %s %s' % (
__version__, __date__)
print u'Lisence: %s %s Author: %s' % (__license__
, __copyright__, __author__)
print usage

if __name__ == "__main__":
main(usage=usage)
タグ:Python Installer
posted by Mire at 03:01 | Comment(0) | TrackBack(0) | Pythonプログラミング | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。

この記事へのトラックバック
月額見放題1,000円開始キャンペーンバナー(画像ありver)
紺碧の艦隊 ルパン三世 GREAT CHASE クリックプロモーション
<< 2013年01月 >>
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31    
カテゴリ
タグクラウド
ファン
利用中のオープンソース
最近のコメント
最近の記事
過去ログ
QRコード
レガシーなアプリはいかが?
Dell 法人のお客様ページ
  • 【法人様向け】デル、お得なキャンペーン情報
  • 法人のお客様向け ストレージソリューション
  • 法人のお客様向け ネットワークソリューション
  • 【SOHO法人様向け】デル・オンライン広告限定ページ
  • デル-個人のお客様ページ
  • 【個人のお客様向け】デル・オンライン広告限定ページ
  • オンライン広告限定キャンペーンページ
  • ソフトウェア&周辺機器 パソコン工房
    ツートップインターネットショップ(twotop.co.jp) マウスコンピューター/G-Tune
  • ×

    この広告は1年以上新しい記事の投稿がないブログに表示されております。