两个Expect工具脚本

Linux有些命令必须手动输入密码,最常见的大概就是ssh了(以密码方式登录)。 当需要批量执行此类任务时,只能一条条人工执行,然后输入密码。 这极大地降低了工作效率,且内心十分痛苦,感觉人就是一台机器。

好在有Don Libes想出来的解决方案Expect[0],可以根据预期输出,自动输入, 这样就可以自动化了。

Expect的语法我不熟悉,从零开始学习回报不高,适用面略窄。 好在Python有很多类似的模块,其中pexpect[2]是纯Python实现的, 没有多余的依赖,安装简单,且项目还在开发维护,值得一用。

这里依照pexpect文档现学了两个工具脚本。

请不要在有安全风险的环境使用类似的脚本,密码一般会记录到shell的执行历史中, 且运行过程中通过ps等命令能够看到密码,存在安全风险!!

  1. rsync同步代码[3]

    工作中有多个编译机器,用于编译不同的项目,但我只想维护一个编码环境,在一个地方写代码。 权衡之下,我最终采用在一台机器上用vim写代码,然后通过rsync同步代码到编译环境的方法(配置vim快捷键)。

    这里还有一个expect版本的[4],如果没有python和pexpect环境, 可以考虑使用,除expect外没有其他依赖。

    #!/usr/bin/env python
    # -*- coding: utf8 -*-
    
    import pexpect
    import sys
    
    def usage():
        sys.stderr.write('''Usage: {} <password> <localpath> <remote>
    '''.format(sys.argv[0]))
        sys.exit(-1)
    
    def rsync(passwd, localpath, remote):
        '''Use pexpect to automatically rsync files'''
        # ignore svn meta files
        cmd = 'rsync -avz --exclude=.svn* -e "ssh -p 22" {} {}'\
            .format(localpath, remote)
        child = pexpect.spawn(cmd)
        #child.logfile = sys.stdout
    
        passwd_sent = False
        i = child.expect(['password:', 'yes/no'], timeout=3)
        if 0 == i:
            child.sendline(passwd)
            passwd_sent = True
        elif 1 == i:
            child.sendline('yes')
    
        if not passwd_sent:
            child.expect('password:', timeout=3)
            child.sendline(passwd)
    
        child.interact()
    
    if '__main__' == __name__:
        if 4 != len(sys.argv):
            usage()
    
        rsync(sys.argv[1], sys.argv[2], sys.argv[3])
    
  2. 多台机器上ssh执行命令[5]

    有时需要到多个机器(有相同的帐号密码)上执行任务,比如查找日志。 于是,有了下面这个脚本。

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    
    import pexpect
    import sys
    
    def usage():
        sys.stderr.write('''Usage: {} <username> <password> <hosts-file> <cmd>
        <hosts-file>    hosts list, one host per line
    '''.format(sys.argv[0]))
        sys.exit(-1)
    
    def do(user, passwd, hosts_file, cmd):
        hosts = []
        with open(hosts_file, 'r') as fp:
            hosts = fp.read().strip().split('\n')
    
        for host in hosts:
            try:
                print('>> Execute "{}" on {}'.format(cmd, host))
                child = pexpect.spawn("ssh {}@{} '{}'".format(user, host, cmd))
                #child.logfile = sys.stderr
    
                passwd_sent = False
                i = child.expect(['password:', 'yes/no'], timeout=3)
                if 0 == i:
                    child.sendline(passwd)
                    passwd_sent = True
                elif 1 == i:
                    child.sendline('yes')
    
                if not passwd_sent:
                    child.expect('password:', timeout=3)
                    child.sendline(passwd)
    
                child.interact()
            except:
                print('>> Failed on {}'.format(host))
    
    if '__main__' == __name__:
        if 5 != len(sys.argv):
            usage()
    
        do(
            sys.argv[1],
            sys.argv[2],
            sys.argv[3],
            sys.argv[4]
        )
    

0 The Expect Home Page

1 Expect manpage

2 Pexpect documentation

3 rsync.py

4 rsync.exp

5 sshdo.py

social