Monday, January 19, 2009

[Python]一个用ssh来远程登录多台机器并执行命令的脚本

功能类似于multissh。事实上我也抄了这个名字//grin。
要求安装了pexpect这个包先。
用法见usage:
Usage: ./multissh.py -f cmdfile -l username -c cmd -n nodesfile -v -r
execut cmd on remote hosts (all hosts in ./hosts.txt by default)
-v verbose
-r recording hosts on which mission succeeded and failed
-l username
-c cmd to be executed remotely
-n file containing the nodes
-f file conaining the cmd
-h show the usage
就是指定一个文件比如nodes.txt以及命令以后,它可以自动登录到nodes.txt包含的节点里执行命令。可以在源文件里替换进你自己的密码,也可以使用公钥密钥登录不需输入密码。指定了v选项的话得到在远端每台主机上的详细输出。指定了r选项的话记录下那些节点成功那些节点失败。
我前面的帖子里有关于ansi_color的一个脚本,拿过来可以配合使用得到彩色输出

使用前可能要根据情况修改prompt变量的定义. 这个变量是关于登录后提示符的正则表达.我根据我自己的几台机器随便写了两个.未必符合你的情况.
#!/usr/bin/python
import sys
import os
import getopt
import pexpect
import re
try:
    from ansi_color import *
except ImportError:
    def color_str(s, *args):
        return s
    fg_green = None
    fg_red = None
    fg_blue = None

prompt = "^.*\(.*\):|\[.*@.*\]|\$"
nl = open("/dev/null", "w")
    
def _print(s):
    if not single_mode:
        print s    

def do(cmds, hostname, username):
    global verbose, quiet, good_hosts
    #print "executing \"%s\""%(repr(cmds))
    ret = 0
    try:
        sshcmd = 'ssh %s'%(hostname)
        if username != None:
            sshcmd = sshcmd + " -l %s"%username
        s = pexpect.spawn(command=sshcmd, timeout=20)
        if verbose:
            s.logfile_read = sys.stdout
            s.setecho(False)
        else:
            s.logfile_read = s.logfile_send = nl 
            s.setecho(False)
        t = 0
        while t < 3:
            i = s.expect([prompt, pexpect.EOF, pexpect.TIMEOUT,\
                          "Are you sure you want to continue connecting (yes/no)?",\
                          "(P|p)assword:"])
            t += 1
            if i == 0:
                break
            elif i == 1:
                _print("End Of File")
                return 1
            elif i == 2:
                _print("time out")
                return 1
            elif i == 3:
                s.sendline("yes")
            elif i == 4:
                s.sendline(password)
        if t >= 3:
            return 1
        if cmds:
            for cmd in cmds:        
                s.sendline(cmd)
                s.expect(prompt)
        else:
            _print("\nEntering interactive mode, please ^] to escape")
            s.interact()
        s.sendline("exit")
        s.close()
        return 0
    except pexpect.ExceptionPexpect:
        return 1

def print_usage():
    print "Usage:\t ./multissh.py -f cmdfile -l username -c cmd -n nodesfile -v -r"
    print "execut cmd on remote hosts (all hosts in ./hosts.txt by default)"
    print "\t-v verbose"
    print "\t-r recording hosts on which mission succeeded and failed"
    print "\t-l username"
    print "\t-c cmd to be executed remotely"
    print "\t-n file containing the nodes"
    print "\t-f file conaining the cmd"
    print "\t-h show the usage"
    print "\t-p password"
    sys.exit(0)

if __name__ == "__main__":
    try:
        opts, args=getopt.getopt(sys.argv[1:], \
            "l:f:n:c:p:vhrq",["login_name", "cmdfile","nodesfile","command","password","verbose","help","recording","quiet"])
    except getopt.GetoptError, err:
        print str(err)
        print_usage()
    if opts == [] and args == []:
        print_usage()
    if args:
        hosts = [args[0]]
    else:
        hosts = []

    cmds = []
    verbose = False
    quiet = False
    username = None
    recording = False
    password = ""
    single_mode = True
    
    for o, ra in opts:
        a = ra.strip("\n")
        if o in ("-h", "--help"):
            print_usage()
        elif o in ("-n", "--nodesfile"):
            hosts = [l.strip(" \t\n") for l in open(a, 'r')]
            single_mode = False
        elif o in ("-c", "--command"):
            cmds = [a]
        elif o in ("-f", "--cmdfile"):
            cmds = [cmd.strip(' \n') for cmd in open(a, 'r')]
        elif o in ("-v",  "--verbose"):
            verbose = True
        elif o in ("-q", "--quiet"):
            quiet = True
        elif o in ("-r", "--recording"):
            recording = True
        elif o in ("-l", "--login_name"):
            username = a
        elif o in ("-p", "--password"):
            password = a

    if not hosts:
        _print("using default ./hosts.txt")
        h = open(os.path.join(os.path.expanduser("."), "hosts.txt"),'r')
        hosts = [dst.strip(' \n') for dst in h]
        
    if recording:
        f_good = open("good_hosts.txt","w")
        f_bad = open("bad_hosts.txt","w")

    good_hosts =[] 
    bad_hosts =[]
    
    for (i, hostname) in enumerate(hosts):
        _print ("%d/%d: ["%(i+1, len(hosts))+ color_str(hostname, fg_blue)+"]")
        ret = do(cmds, hostname, username)
        if verbose:
            print
        if ret == 1:
            bad_hosts.append(hostname)
            _print("["+color_str("Fail!", fg_red)+"]")
            if recording:
                print>>f_bad, hostname
                f_bad.flush()
        else:
            good_hosts.append(hostname) 
            _print ("["+color_str("OK!", fg_green)+"]")
            if recording:
                print>>f_good, hostname
                f_good.flush()
            

    _print ("%d hosts suceed!"%len(good_hosts))
    sys.exit(ret)

[Python]执行一个进程并监视是否超时

用到popen2 module当中的Popen3这个类。用法很简单,创建子进程并立即返回父进程。然后可以用poll()函数检查子进程是否已经结束(未结束的话返回值-1)。
import popen2
def _popen(cmd, timeout = 10, num_retry = 3, logfile = sys.stdout):
    i = 0
    is_timeout = False
    while i <= num_retry:
        print "%dth try"%i, cmd
        sys.stdout.flush()
        i += 1
        t0 = time()
        P = popen2.Popen3(cmd, True)
        prompt = False
        while time() < t0 + timeout and P.poll() == -1:
            sleep(0.1)
        sts = P.poll()
        if sts == -1:
            logfile.write(color_str("command [%s] timeout\n"%(cmd), fg_red))
            if i < num_retry:
                logfile.write(color_str("terminate and try again\n", fg_red))
            logfile.flush()
            is_timeout = True
            os.kill(P.pid, signal.SIGTERM)
        elif sts != 0:
            for l in P.childerr.readlines():
                logfile.write(l)
            if i < num_retry:
                logfile.write(color_str("try again\n", fg_red))
            logfile.flush()
            is_timeout = False
        else:
            is_timeout = False
            break
    logfile.write(color_str("return "+str(sts)+"\n", fg_red))
    sys.stdout.flush()
    if is_timeout:
        return (sts, open("/dev/null", "r"))
    else:
        return (sts, P.fromchild)

按说popen2这个module应该已经deprecated,被subprocess module所取代。不过subprocess.Popen的poll()函数似乎有bug,总是返回None。所以只好还用“古老”的module。

Saturday, January 10, 2009

[Latex]MacOS上Latex几个IDE的比较

工欲善其事,必先利其器。因为要在macbook上用latex写paper,需要在几种latex编辑器的选择里找一种最舒服的。我先后尝试了如下的一些:
  • TexShop。免费的一个编译环境。最大的优点就是免费。打开的文件一多的时候操作起来不方便,因为不能用标签选择文件,使用起来很不舒服。
  • TextMate。是一个综合性的文本编辑器。界面漂亮,使用很友好。但是最大的问题是要钱
  • BBEdit。也很不错的一个文本编辑器。但是要钱。TextWrangler是其免费版,但是功能受限制,不爽。
  • Emacs+AUCTeX。还是这个好,免费,而且界面也可以搞的很漂亮。