お題「vmlinuz を解凍して vmlinux を取りだそう」。
環境は Mandriva Linux 2010.0。
Traditionally, when creating a bootable kernel image, the kernel is also compressed using the zlib algorithm, or since Linux 2.6.30[1], using LZMA or BZIP2, which requires a very small decompression stub to be included in the resulting image.
vmlinuz は gzip で圧縮されていることが知られています。Kernel 2.6.30 以降は LZMA か BZIP2 が使われていることもあるらしいですが、結論から言うと今回のは gzip。
ただし、ただの gzip 圧縮というわけではなく、16進数ダンプやらなにやらして確認して、ようやく解凍できました。
普通に gunzip を試みる
普通に gunzip を試してみます。
kei@frederica ~ $ gunzip /boot/vmlinuz-2.6.31.12-desktop-1mnb gzip: /boot/vmlinuz-2.6.31.12-desktop-1mnb: unknown suffix -- ignored
gunzip は拡張子を見るプログラムだったみたいですよ。
拡張子をつけて改めて。
kei@frederica ~ $ cp /boot/vmlinuz-2.6.31.12-desktop-1mnb ./vmlinuz.gz kei@frederica ~ $ gunzip ./vmlinuz.gz gzip: ./vmlinuz.gz: not in gzip format
!? gzip じゃねーよとおこられます。
ちなみにこのあと、BZIP2 や LZMA を試しても同様に撃沈。
16進数ダンプして gzip ヘッダーを確認する
od (8進数ダンプツール) にオプションをつけて16進数出力して確認してみます。
od -A d -t x1 /boot/vmlinuz-2.6.31.12-desktop-1mnb > vmlinuz.dump
-A d で表示されるオフセットの基数は10進数に。
-t x1 で16進数出力に。
gzip のヘッダーは「1f 8b 08 00」。なんか頭にいないんですけど。
0015952 c7 c1 c0 16 2c 00 48 c1 e9 03 fd f3 48 a5 fc 5e 0015968 48 8d 83 20 d8 2b 00 ff e0 1f 8b 08 00 d8 a0 5e 0015984 4b 02 03 ec fd 0b 7c 14 d5 fd f8 ff cf e6 02 41
ファイルに落として確認するのは面倒ですので、grep に渡すだけでいいです。
kei@frederica ~ $ od -A d -t x1 /boot/vmlinuz-2.6.31.12-desktop-1mnb | grep '1f 8b 08 00' 0015968 48 8d 83 20 d8 2b 00 ff e0 1f 8b 08 00 d8 a0 5e
gzip ヘッダーの開始バイト位置を確認する
0015968 48 8d 83 20 d8 2b 00 ff e0 1f 8b 08 00 d8 a0 5e
15968 から数えて 1f は 15968 + 9 = 15977。
つまりここまでスキップしてから gzip として解凍すれば、解凍できるはず。
dd でバイトスキップして zcat に渡す
dd でインプットファイルに /boot/vmlinuz-2.6.31.12-desktop-1mnb を食べさせて、ブロックサイズを1で 15977 個スキップします (0 から数えるから)。
kei@frederica ~ $ dd if=/boot/vmlinuz-2.6.31.12-desktop-1mnb bs=1 skip=15977 | zcat > vmlinux gzip: stdin: decompression OK, trailing garbage ignored
「decompression OK」!
strings で vmlinux を参照
無事解凍できましたが、当然バイナリ。
でも、Kernel version とかの文字列が含まれているはずです。
kei@frederica ~ $ strings vmlinux | grep "Linux version" Linux version 2.6.31.12-desktop-1mnb (qateam@titan.mandriva.com) (gcc version 4.4.1 (GCC) ) #1 SMP Tue Jan 26 02:59:07 EST 2010
とれたー!
結論
vmlinuz は gzip ヘッダーが頭にいないので、dd でスキップして解凍しましょう。
ちなみに fedora 8 の xen kernel は拡張子を .gz にしたうえで gunzip したら普通に解凍できたり。xen kernel は単純な gzip なのかなー。
Lightweight Language Television
LLTV おつかれさまでした! # もう先週の話です。
VAIO 持って行ったけど、iPhone から twitter に投げるだけでログが済んでしまいました。昨年からの大きなライフスタイルの違いを実感。
今回も10時間という長時間の LL。
言語話が少ない印象でした。
昨年は処理系の話とかコードの話とか熱かった気がするけど。
Apribase » Lightweight Language Future
Apribase » Lightweight Language Spirit
ログを残すと同時に、リアルタイムで会場内の twitter ユーザの声も聞けたのが楽しかったかな。
たしか2007年の LL 魂のとき、Lingr を使ってチャットがあった気がするけど、あのときのときめきの再来。
以下、twitter で流してた分のログ。
10:25 開会宣言
LLTV はじまるよー
posted at 10:29:05
10:30 朝から生テレビ
しろうさんの昔話はじまた。 #LLTV
posted at 10:44:44
pypy いいよ pypy。しろうさんも興味もったよ。
posted at 10:50:17
上位から下位まで楽しいのはゲーム業界。ですよねー。 #LLTV
posted at 11:01:35
これ、話、落ちるのかしら。めっさ飛んでる。司会大変。 #LLTV
posted at 11:07:38
Clousure きたー #LLTV
posted at 11:12:54
Squeak とかに繋がる振りと思ったのにクラウドに飛んだ。。 #LLTV
posted at 11:27:19
「今日あたりのブログでグズグズ感満載」 #LLTV
posted at 11:51:08
12:00 LL フィーリングカップル
LL フィーリングカップルはじまるよー #LLTV
posted at 12:04:23
デートでペアプロいいじゃない。 #LLTV
posted at 12:25:24
ここいちさん「同じ職場でなければ」生々しいな #LLTV
posted at 12:28:46
女性らしいコードってなに。 #LLTV
posted at 12:30:23
女性でもラムダラムダ言ってますが。 #LLTV
posted at 12:35:14
ところで、LL 全く関係ない。 #LLTV
posted at 12:43:39
牧さんのライフはゼロよ! #LLTV
posted at 12:53:56
しおりさんの意見に同意。近いほうがいいです。 #LLTV
posted at 13:01:07
13:00 昼休み
ひるやすみー http://movapic.com/pic/200908291331514a98af377955b
posted at 13:31:55
渡る世間は雲ばかり
渡る世間は雲ばかり。 #LLTV
posted at 14:32:25
MS の方が Mac 使うのはツッコミ待ちと理解。 #LLTV
posted at 14:40:39
しかも Windows7 デスクトップ見せられた。釣り針いっぱい。 #LLTV
posted at 14:43:09
まとめ。MS の人は Macbook に Bootcamp で Windows7 で発表。 #LLTV
posted at 14:46:13
誘惑に負けないひがさんのテストケース。頑張って司会さん。 #LLTV
posted at 15:07:05
「ひがさんのが眠い」とか、出だしから飛ばす高井さん。 #LLTV
posted at 15:11:32
第一言語 Java だけど、この会場で手をあげられなかった(ぇー #LLTV
posted at 15:13:38
ぶ、Mongrel 開発者とんずらしてたの今知ったよ! #LLTV
posted at 15:14:36
昨年と比べ、Ruby 周りも分かる今、JRuby の理解度に隙はなかった。 #LLTV
posted at 15:17:27
JRuby-Rack #LLTV
posted at 15:20:33
「詳細はググれ」 #LLTV
posted at 15:21:11
スフィンクスってドキュメントツール?あとでぐぐる。 #LLTV
posted at 15:40:04
高井さんが萌える。「ゴメンね?」 #LLTV
posted at 15:58:36
プロトタイピング〜「もの作り」の流儀〜
ビデオプロトタイプ。ハードウェアスケッチ。 #LLTV
posted at 16:25:41
十時間は伊達じゃなす。ねむいー。 #LLTV
posted at 17:09:30
17:20 夕休み
18:00 大改善!!劇的ビフォーアフター
劇的ビフォーアフター始まりました。 #LLTV
posted at 18:05:36
Vimperator で SL コマンド実装。 #LLTV
posted at 18:28:29
lucky☆st◯r #LLTV
posted at 18:48:02
普通は ls なんて使わない。 buffer! つかうので。 #LLTV
posted at 18:49:14
Real/Macro Metaprogramming on C #LLTV
posted at 18:50:46
BSD の ls ソースは優秀。 #LLTV
posted at 18:53:04
cmp.c 冗長なコード。こうかいかんすうがつかえない。 #LLTV
posted at 18:54:22
util.c クロージャが使えない。 foreach がない。 #LLTV
posted at 18:55:46
CiSE C In S Expression #LLTV
posted at 18:58:03
マクロでコード生成 繰り返しパターンの除去。DSL も実装。 #LLTV
posted at 19:02:34
マクロクラブ マクロを書くな。 それがカプセル化の唯一の手段ならマクロを書け。関数よりいいなら書け。 #LLTV
posted at 19:06:49
クロージャ(JVM LISP のほう) より。しろうさん翻訳中。 #LLTV
posted at 19:08:52
バージョン管理もデータベースも涙目。テキスト最強説展開中。 #LLTV
posted at 19:15:17
LL レッドカーペット
ライトニングトーク準備中ー http://movapic.com/pic/200908291938294a990525d4db0
posted at 19:38:33
しばらくお待ちください。 http://movapic.com/pic/200908291942414a99062115c26
posted at 19:42:45
ゆっくりしていってね。 #LLTV
posted at 19:57:34
形態素解析。全裸で。 #LLTV
posted at 19:59:00
IRC の発言がすべて全裸に。 #LLTV
posted at 20:00:30
Twitter zenra_bot #LLTV
posted at 20:01:16
これを全裸イナーといいます。 #LLTV
posted at 20:03:18
Konoha せんせーw #LLTV
posted at 20:10:50
なるとより。木の葉の里。最近潰れましたけど。 #LLTV
posted at 20:11:40
LL でカーネルを書こう。 #LLTV
posted at 20:12:51
いや無理。 #LLTV
posted at 20:13:01
Konoha が落ちると、カーネルも落ちます。 #LLTV
posted at 20:14:38
TVML 一行イベントでアニメ。 #LLTV
posted at 20:25:28
ビッチー シュール…! #LLTV
posted at 20:26:13
小さい女の子が話す pykook。 #LLTV
posted at 20:29:02
残り一分が虚しかった。 #LLTV
posted at 20:33:07
H8 LED マトリクス。 #LLTV
posted at 20:35:39
PySerial が実にクール。 #LLTV
posted at 20:36:10
リモコンでテレビ をロボットハンドで。 #LLTV
posted at 20:37:46
ほりぐれさんの漫才はじまた。 #LLTV
posted at 20:38:43
番組終了です。おつかれさまでした。 http://movapic.com/pic/200908292050124a9915f4d73a6
posted at 20:50:26
帰り
帰りの電車なう。つかれたー。 #LLTV
posted at 21:21:22

C++ で外部プロセスを呼び出そうとしたらすごく大変だったから、python の commands.getoutput(”ls -l”) みたいに一行で書けて標準出力が返ってくれば幸せになれるのに・・・!という経緯です。
CreateProcess
今回は WIndows 用に調べてたんで MSDN。
CreateProcess 関数
泣きたくなるような引数の数に加え、型が外国語すぎて読めません;
それでも一応頑張って読んでみると、STARTUPINFO ってのを外から与えると、構造体の中を破壊してきて標準出力とかがこの中に書き込まれるみたいですよ。
STARTUPINFO
このへんで投げ出したけど、真面目に実装するとけっこう長くなるかんじ。ハンドルって概念が分からないんですよ。なにそれ。
popen/pclose
まだこっちのが分かるよねってのが C 標準関数の popen/pclose。
Manage of POPEN
Windows でも _popen/_pclose で実装されてるみたいで、とりあえずこっちで書いてみることに。
python commands
目指すインターフェースは以下なかんじ。とりあえず getoutput だけ。
16.16 commands — コマンド実行ユーティリティ
commands.hpp
インターフェースをまねるとこんなかんじですよね。
インスタンス生成とかせずに、シンプルに関数呼び出しで扱える形に。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | /* * File: commands.hpp * Author: kei * * Created on 2009/08/18 */ #include <string> #ifndef _COMMANDS_HPP #define _COMMANDS_HPP namespace commands { const std::string getoutput(const std::string& command); } #endif /* _COMMANDS_HPP */ |
commands.cpp
実装。
C 言語特有のエラーコードを返す関数を、全部例外投げるようにラップしてみたけど、finally がないからクローズ処理がまとめられなかったり、throws がないから再 throw になっちゃったり。
あと Java の IOException みたいな例外定義がデフォルトでなくて、そもそも catch しなくてもコンパイル通るとか。
それぞれの例外クラスを定義して、catch で分けるのが正しいんですが、とりあえず std::runtime_error 投げたんで、よい子はマネしないでね。
popen がファイルディスクリプタを返してくるんですが、この子からファイルストリームを生成するうまい方法がなくて(昔のコンパイラはコンストラクタに喰わせて fstream 作れたみたいだけど)、素直に C 言語な実装に。
あと必ず pclose で閉じること!って書いてあったから、boost::shared_ptr なんか使って解放しちゃ危なそうってことで boost も出番なし。
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | /* * File: commands.cpp * Author: kei * * Created on 2009/08/18 */ #include <cstdio> #include <sstream> #include <stdexcept> #include "commands.hpp" /** * 指定したコマンドのプロセスへのファイルディスクリプタを返します。 * 使用後は close_process(const FILE* process) でクローズしてください。 * プロセスのオープンに失敗した場合は例外を投げます。 * @param command * @return プロセスへのファイルディスクリプタ * @throw std::runtime_error */ FILE* open_process(const std::string& command) { FILE* process = popen(command.c_str(), "r"); if (process == NULL) { throw std::runtime_error("open " + command + " failed."); } return process; } /** * 実行プロセスの出力を返します。 * @param process * @return プロセス実行出力 */ const std::string read_process(FILE* process) { std::stringstream output; char buffer[128]; // allocate memory while (fgets(buffer, sizeof (buffer), process)) { output << buffer; } return output.str(); } /** * 指定したプロセスをクローズします。 * プロセスのクローズに失敗した場合は例外を投げます。 * @throw std::runtime_error */ void close_process(FILE* process) { const int result = pclose(process); if (result == -1) { throw std::runtime_error("close_process faild."); } } /** * 指定したコマンドをシェル経由で実行し、出力を返します。 * プロセスのオープン/実行/クローズのいずれかに失敗した場合は例外を投げます。 * @param command コマンド * @return コマンド出力 * @throw std::runtime_error */ const std::string commands::getoutput(const std::string& command) { FILE* process = NULL; std::string output; try { process = open_process(command); output = read_process(process); } catch (std::runtime_error error) { // finally 処理の代わりに、例外時にもクローズ処理を書いてあります。 // open/read の例外時のため、close の例外確認は行いません。 if (process != NULL) { close_process(process); } throw error; } // 正常時の close 処理です。例外確認も行います。 if (process != NULL) { try { close_process(process); } catch (std::runtime_error error) { throw error; } } return output; } |
main.cpp
date コマンドを打ってみた例。
まあこんなとこでしょ。
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 | /* * File: main.cpp * Author: kei * * Created on 2009/08/18 */ #include <cstdlib> #include <iostream> #include <stdexcept> #include "commands.hpp" /** * メインエントリです。 * 以下は date コマンドを実行するサンプルです。 * @param argc * @param argv */ int main(int argc, char** argv) { try { std::cout << commands::getoutput("date") << std::endl; } catch(std::runtime_error error) { std::cerr << error.what() << std::endl; } return (EXIT_SUCCESS); } |
/usr/lib/python2.6/commands.py
python の実装を真面目に読んでみる。
os.popen() を使ってるみたい。
"""Execute shell commands via os.popen() and return status, output.確かに。
def getstatusoutput(cmd): """Return (status, output) of executing cmd in a shell.""" import os pipe = os.popen('{ ' + cmd + '; } 2>&1', 'r') text = pipe.read() sts = pipe.close()
/usr/lib/python2.6/os.py
subprocess の Popen からパイプを生成してるもよう。
import subprocess PIPE = subprocess.PIPE p = subprocess.Popen(cmd, shell=True, bufsize=bufsize, stdin=PIPE, stdout=PIPE, close_fds=True) return p.stdin, p.stdout
/usr/lib/python2.6/subprocess.py
あー、Windows 版はやっぱり CreateProcess を使って実装してるのね。
On UNIX, with shell=False (default): In this case, the Popen class
uses os.execvp() to execute the child program. args should normally
be a sequence. A string will be treated as a sequence with the string
as the only item (the program to execute).On UNIX, with shell=True: If args is a string, it specifies the
command string to execute through the shell. If args is a sequence,
the first item specifies the command string, and any additional items
will be treated as additional shell arguments.On Windows: the Popen class uses CreateProcess() to execute the child
program, which operates on strings. If args is a sequence, it will be
converted to a string using the list2cmdline method. Please note that
not all MS Windows applications interpret the command line the same
way: The list2cmdline is designed for applications using the same
rules as the MS C runtime.
startupinfo の文字が。やっぱり頑張らないとかー。
# Start the process try: hp, ht, pid, tid = CreateProcess(executable, args, # no special security None, None, int(not close_fds), creationflags, env, cwd, startupinfo)
最後に
boost::process みたいなのないですか。いやぐぐるとひっかかるんですけど、これ boost パッケージに含まれてます?
Pstreams ってのもあったけど使ってる人いるのかしら?
特になければ、今回作ったものをもう少しちゃんと実装して使い回していく方向かなあ。ぐぐっても出てこないあたり需要ないのか知らないけど、Windows で ssh ライブラリがないからこれで叩こうと思ったわけで。
PuTTY のソースも見たし、python paramiko も試したけど、大人の事情がもにょもにょだったので。
C++ 素人につきリファクタ職人の降臨が望まれます。
paramiko: ssh2 protocol for python
Pythonで ssh 接続するのはすごく簡単でした。
Linux なら command で ssh を叩けばいいんですけど、Windows だとそうもいかなくて。
cygwin 入れるのもなんだし、PuTTY はオープンソースだから組み込むってのもできるけど・・・。
あれ?Python のコレ、Windows でも動くんじゃない?というときめく期待のもとに paramiko 初体験という経緯なのです。
LGPL だから気をつけないといけないけど、これだけ使いやすくて更新もされているわけで。
どっかで見たことあると思ったら、bazaar とかでも依存ライブラリに書いてあったし、けっこうあちこちで使われてそうというのもいいかんじ。
C++ から boost.python で呼ぶなり、単純に外部プロセスとして呼び出すなりで、いろいろできそう。
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 32 33 34 | #!/usr/bin/python # -*- mode:python; coding:utf-8 -*- # paramiko_test.py import paramiko __author__ = "kei" __date__ = "$2009/08/04 22:04:05$" def command(): return "df -h" def hostname(): return "apribase.net" def username(): return "apribase" def password(): return "foobar" if __name__ == "__main__": client = None try: client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) client.connect(hostname(), username=username(), password=password()) stdin, stdout, stderr = client.exec_command(command()) print "hostname: " + hostname() print "command: " + command() print stdout.read() finally: if client != None: client.close() |
勉強会のときにも使った python 製世代管理バックアップスクリプトです。
rsync3 のプロトコルによる高速化と、–rsync-path と sudo の組み合わせによる一般ユーザ権限ログインによる実行、そして –link-dest を使ったハードリンクによる差分バックアップを実現しています。
スクリプトでまかなっているところは、各オプションの指定方法が分かりにくかったり、空のディレクトリと同期してデータが全滅したりするのを防ぐところ。あと毎日のバックアップと毎週のスナップショットをとってローテーションをまわすところ。
スナップショットをとる曜日や、システムユーザ(一般ユーザ権限)等、直接スクリプトを編集して運用するつもりなので、下記に公開してるものをそのまま使われることは想定していません。
twitter 上でも少し話が出ましたが、元々 iSCSI 環境で問題が出たのと、GFS 等を利用できない環境のために作成したものです。
使い方
バックアップサーバ側で実行し、バックアップ対象のデータを pull します。
python pull_backup.py --hostname="apribase" --directory="/home/"
上記のように実行すると、hostname 用のディレクトリが /home/ 配下に生成され、日付付きでディレクトリが保存されます。
最新版にシンボリックリンクがはられ、次のバックアップ時には前回からの差分(正確には重複ファイルをハードリンクするのであって、差分という言葉は若干違う)を保存します。
home -> /home/apribase/home-200907-22
/home/apribase/home-200907-22
拡張オプションとして、exclude に対応してあります。
カンマ区切りで指定すると、rsync オプションの exclude を複数指定する記述に展開します。
python pull_backup.py --hostname="apribase" --directory="/home/" --exclude="db*,socket,server.pem,tls_sessions.db"
pull_backup.py
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | #! /usr/bin/python # -*- mode:python; coding:utf-8 -*- # pull_backup.py import calendar import commands import datetime import os.path import socket from optparse import OptionParser __author__ = "kei" __date__ = "$2009/07/22 02:43:23$" # ============================================================================ # setup option parser # ============================================================================ parser = OptionParser(usage="python %prog --hostname=HOSTNAME --directory=DIRECTORY --exclude=FILE\n ex: python %prog --hostname=\"apribase\" --directory=\"/home/\" --exclude=\"db*,socket,server.pem,tls_sessions.db\"") parser.add_option("--hostname", dest="hostname", help="--hostname=\"apribase\"") parser.add_option("--directory", dest="directory", help="--directory=\"/home/\"") parser.add_option("--exclude", dest="exclude", help="--exclude=\"db*,socket,server.pem,tls_sessions.db\"") options, args = parser.parse_args() # ============================================================================ # target strings # ============================================================================ def hostname(): return options.hostname def directory(): return options.directory def exclude(): str = "" if not options.exclude == None: for e in options.exclude.split(","): str += "--exclude=" + "\"" + e + "\' " return str def domain(): return ".apribase.net" def rsync_user(): return "rsync" def rsync_bin(): return "/opt/rsync/bin/rsync" # ============================================================================ # rsync # ============================================================================ # rsync -azXA -e ssh --delete --rsync-path="sudo /opt/rsync/bin/rsync" --link-dest="/home/apribase/home/" rsync@apribase.apribase.net:/home/ /home/apribase/home-2009-07-22/ def rsync(): return rsync_bin() + " -azXA -e ssh --delete " + rsync_path() + " " + link_dest() + " " + exclude() + " " + src() + " " + dst() # rsync@apribase.apribase.net:/home/ def src(): return rsync_user() + "@" + hostname() + domain() + ":" + directory() + "/" # /home/apribase/home-2009-07-22/ def dst(): return "/home/" + hostname() + directory() + "-" + datetime.date.today().isoformat() + "/" # --rsync-path="sudo /opt/rsync/bin/rsync" def rsync_path(): return "--rsync-path=\"sudo /opt/rsync/bin/rsync\"" # --link-dest="/home/apribase/home/" def link_dest(): return "--link-dest=\"/home/" + hostname() + directory() + "/" + "\"" # prepare for rsync def mkdir(): return "mkdir -p " + dst() # ============================================================================ # lotate backups # ============================================================================ # lotate backup directory # 7/20 7/21 7/22 # 06-30 06-30 07-07 # 07-07 07-07 07-14 # 07-14 07-14 07-21 # ----- ----- ----- # 07-20 07-21 07-22 def lotate(): def snapshot_day(): return calendar.MONDAY # /home/apribase/home-2009-07-22 def today(): return "/home/" + hostname() + directory() + "-" + datetime.date.today().isoformat() # /home/apribase/home-2009-07-21 def yesterday(): return "/home/" + hostname() + directory() + "-" + (datetime.date.today() - datetime.timedelta(1)).isoformat() # /home/apribase/home-2009-06-30 def oldest(): return "/home/" + hostname() + directory() + "-" + (datetime.date.today() - datetime.timedelta(22)).isoformat() # /home/apribase/home def latest(): return "/home/" + hostname() + directory() # rm -rf /home/apribase/home-2009-07-21; rm -f /home/apribase; ln -s home-2009-07-22 /home/apribase/home def daily(): return "rm -rf " + yesterday() + "; rm -f " + latest() + "; ln -s " + os.path.basename(directory()) + "-" + datetime.date.today().isoformat() + " " + latest() # rm -rf /home/apribase/home-2009-06-30; rm -f /home/apribase; ln -s home-2009-07-22 /home/apribase/home def weekly(): return "rm -rf " + oldest() + "; rm -f " + latest() + "; ln -s " + os.path.basename(directory()) + "-" + datetime.date.today().isoformat() + " " + latest() if((datetime.date.today() - datetime.timedelta(1)).weekday() == snapshot_day()): return weekly() else: return daily() # ============================================================================ # check options # ============================================================================ # script needs hostname and directory. def check_none(): if hostname() == None: parser.error("set hostname.") if directory() == None: parser.error("set directory.") # valid hostname? def check_hostname(): try: socket.gethostbyname(hostname() + domain()) except socket.gaierror: parser.error(hostname() + domain() + " is invalid hostname or ipaddress.") # exist? def check_directory(): # python -c "import os; print os.path.exists(\"/\")" def command_python(): return "python -c \"import os; print os.path.exists(\\\"" + directory() + "\\\")\"" # ssh rsync@apribase.net 'python -c \"import os; print os.path.exists(\"/\")\"' def command_ssh(): return "ssh " + rsync_user() + "@" + hostname() + domain() + " \'" + command_python() + "\'" # /home/apribase/ is TRUE, apribase is FALSE if not (directory().startswith("/")): parser.error(directory() + " is not full path.") # remote directory is not exist... print command_ssh() if (commands.getoutput(command_ssh()) == "False"): parser.error(hostname() + ":" + directory() + " is not exist.") # /home/ to /home def remove_end_slash(directory_): if (directory_.endswith("/")): return directory_.rstrip("/") return directory_ if __name__ == "__main__": check_none() check_hostname() check_directory() options.directory = remove_end_slash(options.directory) # override if not (os.path.exists(dst())): print mkdir() commands.getoutput(mkdir()) print rsync() print commands.getoutput(rsync()) print lotate() print commands.getoutput(lotate()) |
commands でシェルコマンドを叩く
commands.getoutput でシェルのコマンドを叩けます。
結果が return されるので print 文で出力してあげましょう。
これでシェルスクリプトが書けない僕でも python のおかげで結婚できました状態。
import commands print commands.getoutput("echo HelloWorld!")
optparse でオプション指定
コマンド引数が args に。-h –help などが options にタプルで入ります。
add_options で追加していけます。
-d –directory みたいに2つセットしないといけないというわけでもなく、片方だけでも大丈夫でした。
生の argv を見るよりエラーチェックがやりやすいのが嬉しいかな。
from optparse import OptionParser parser = OptionParser(usage="python %prog --hostname=HOSTNAME --directory=DIRECTORY --exclude=FILE\n ex: python %prog --hostname=\"apribase\" --directory=\"/home/\" --exclude=\"db*,socket,server.pem,tls_sessions.db\"") parser.add_option("--hostname", dest="hostname", help="--hostname=\"apribase\"") parser.add_option("--directory", dest="directory", help="--directory=\"/home/\"") parser.add_option("--exclude", dest="exclude", help="--exclude=\"db*,socket,server.pem,tls_sessions.db\"") options, args = parser.parse_args() print options print args
os.path でディレクトリを操作する
os でディレクトリの操作などが行えます。ディレクトリの存在確認など。
import commands import os.path if not (os.path.exists("/home/apribase/workspace")): print commands.getoutput("mkdir /home/apribase/workspace")
ssh とワンライナーを使った離れ業
ssh の引数にコマンドを打つと、リモートでコマンドを実行してくれます。
これを使って python -c のワンライナーを飛ばすと、リモート側で実行できるので、それを commands で実行してリモートのディレクトリの存在を確認したり。
シングルクォーテーションとダブルクォーテーションの入れ子にものすごく悩みましたが。
ssh rsync@apribase.net 'python -c \"import os; print os.path.exists(\"/\")\"'
文字列操作
ここでは directory_ が文字列ということで。
末尾の文字をチェックしたり、末尾の指定文字を消した文字列を生成したり。
def remove_end_slash(directory_): if (directory_.endswith("/")): return directory_.rstrip("/") return directory_</code>
文字列と数字を変換する関数
str(1), int(”1″)。こんなかんじだったかな。
文字列結合のときに数字は結合できないので str() 関数で文字列を返す必要が出てきます。
オブジェクトに toString とかじゃないのですよ。
socket の使用も短く書ける
socket も便利なメソッドが揃っていてすぐに使えます。
例外等も公式ドキュメントにしっかり書いてあるので簡単でした。
import socket try: socket.gethostbyname(hostname() + domain()) except socket.gaierror: parser.error(hostname() + domain() + " is invalid hostname or ipaddress.")
関数の中に関数?
そういうこともできるんです。
スコープ内でしか使わないからって意味でしか使わなかったけど、表現としてどういう美しさを表現するためのものかはまだ知らないのです。
あと、定数(変数)を返したかっただけなので、素直に代入演算子使ったほうがいいと思います。
関数スタックに積まれて遅くなるだけかと。
def lotate(): def snapshot_day(): return calendar.MANDAY
if not
曰く、「かっこいい」らしい。
if not (directory().startswith("/")):>
行コメントは?
# で。
# ============================================================================ # setup option parser # ============================================================================
if __name__ == “__main__”: ?
python pull_backup.py のように実行されたとき、ここが実行されるのですよ。
datetime で日付操作
today() で今日が取得できて、timedelta で差分を足し引きすることで調整できたり。
isoformat() を使うと 2009-07-22 みたいに整形してくれます。
import datetime # /home/apribase/home-2009-07-22 def today(): return "/home/" + hostname() + directory() + "-" + datetime.date.today().isoformat() # /home/apribase/home-2009-07-21 def yesterday(): return "/home/" + hostname() + directory() + "-" + (datetime.date.today() - datetime.timedelta(1)).isoformat()
シェルスクリプトが書けない僕でも
python のおかげで身長が伸びて結婚できて幸せになれるよ!







