V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
ouiki
V2EX  ›  Python

[求助] subprocess 的 stdout 堵塞问题

  •  
  •   ouiki · 2017-08-28 08:25:41 +08:00 · 6395 次点击
    这是一个创建于 2635 天前的主题,其中的信息可能已经有所发展或是发生改变。
    要求是这样的:
    1 ) 登入 mysql 服务器( mysql -h localhost -uroot -p1234 )。
    2 ) 输入 mysql 内部命令 show databases,如果返回的内容出现 mysql (存在 mysql DB )就立刻强制退出整个 python 程序。
    (关于要求 2 的解释,假如 show databases 的返回内容是 information_schema \r\n mysql \r\n test。不要等到 test 出现,马上就退出或者杀死该程序)

    我认为只有用 subprocess 能够比较好的完成以上功能,所以以下都是以使用 subprocess 为前提。

    个人试了好多方法,都不成功。
    方法 1:把 stdout 放到线程里


    def stdout_theard(p_stdout):
    time.sleep(0.01)
    for i in range(3000):
    print p_stdout.readline()

    s_command = 'mysql -h localhost -uroot -p1234'
    sub_process = subprocess.Popen(command , stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell = True)
    thread_read_output = threading.Thread(target=stdout_theard, args=(sub_process.stdout,))
    thread_read_output.setDaemon('True')
    thread_read_output.start()

    sub_process.stdin.write('show databases;\r\n')

    方法 2:把 stdout 重定向到文件里
    s_command = 'mysql -h localhost -uroot -p1234'
    f_out = tempfile.TemporaryFile(mode='w+')
    f_err = tempfile.TemporaryFile(mode='w+')
    sub_process = subprocess.Popen(command , stdin = subprocess.PIPE, stdout = f_out, stderr = f_err, shell = True)

    或者:
    os.dup2(sub_process.stdout.fileno(), f_out.fileno())

    以上方法都没成功。
    希望前辈高手们指点。

    另,以上只是用 mysql 打了个比方,实际的环境不太好说。
    必须得调用一个 exe ( cisco anyConnect ),得到返回值。但登陆这个 exe 的时候,一旦给定的用户名密码错误,它会一直用这组错误的用户名密码试,直到该用户被锁死。
    退出的原因就是,在第一次用户名密码错误时,就退出,避免 exe 反复试,导致锁死。
    26 条回复    2017-08-31 13:33:26 +08:00
    21grams
        1
    21grams  
       2017-08-28 09:54:22 +08:00 via Android
    没成功具体是什么问题?
    ouiki
        2
    ouiki  
    OP
       2017-08-28 10:08:51 +08:00
    放到线程里仍旧是阻塞。
    重定向到文件里,写不进去。文件总是空,只有强制退出( Ctrl+C )后,才能写到文件里,看来也是阻塞的问题。

    大牛,或者说有没有成功的经验。很可能我的写法也有问题。
    araraloren
        3
    araraloren  
       2017-08-28 10:12:18 +08:00
    经过测试你需要 添加一个 -n 在 mysql 的命令行里
    它默认开启了缓冲
    araraloren
        4
    araraloren  
       2017-08-28 10:13:59 +08:00
    我用 Perl 6 来测试。。
    my $p = Proc::Async.new(<mysql -n -P3306 -u ovirt -pdefault>, :w);
    $p.stdout.tap(&say);
    $p.stderr.tap(&say);
    my $pp = $p.start;
    await $p.put("show databases;\r\n");
    say "WAITING OVER";
    await $pp;

    输出
    Database
    information_schema
    mysql
    performance_schema
    topbandit
        5
    topbandit  
       2017-08-28 10:27:40 +08:00   ❤️ 1
    @ouiki
    linux pipe size 大小 512B*8= 4096Bytes,Pipe 满了就会阻塞。
    处理方法
    1 )即时取出 stdout,边读边写入文件,适用输出无穷大
    2 ) Pipe.commucate(),读入内存,适用输出小的情况
    ouiki
        6
    ouiki  
    OP
       2017-08-28 10:30:45 +08:00
    因为 python 的 subprocess 的 stdin,stdout 有阻塞的问题,所以我不会处理。
    perl 没有阻塞的问题么?这到是个好消息,我可以用 perl 试试。
    araraloren
        7
    araraloren  
       2017-08-28 10:51:52 +08:00
    @ouiki 你看了我说的话?我是说 mysql 默认开启了输出的缓冲,加上 -n 关掉估计就可以了。。
    ouiki
        8
    ouiki  
    OP
       2017-08-28 11:01:44 +08:00
    @araraloren 谢谢回复,学到了。
    ouiki
        9
    ouiki  
    OP
       2017-08-28 11:02:42 +08:00
    @topbandit 所谓“及时取出 stdout,边读边写”,的意思是 p.stdout.flush() 么?
    好像抓到点什么了?
    ouiki
        10
    ouiki  
    OP
       2017-08-28 11:05:13 +08:00
    @topbandit stdout,stderr = Pipe.commucate() 是不是等到进程结束才输出?这个在我的程序里不适用。不能等到结束,还要有后续的动作。(事实上是用 cisco anyConnect 建立连接之后,测试上网)
    topbandit
        11
    topbandit  
       2017-08-28 11:12:40 +08:00
    @ouiki 边读边写:
    while 1:
    line = p.stdout.readline()
    write(line)

    前边没仔细看你的需求,如果仅仅是 show databases 的输出,output 多大,会引起 pipe 阻塞?
    topbandit
        12
    topbandit  
       2017-08-28 11:14:50 +08:00
    前面多余,你用一条 mysql 命令搞好了
    topbandit
        13
    topbandit  
       2017-08-28 11:18:44 +08:00   ❤️ 1
    mysql -u -p -e
    此帖终结
    guyskk
        14
    guyskk  
       2017-08-28 13:11:14 +08:00 via Android
    缓冲 IO 的问题。PIPE 和普通文件默认都是全缓冲的,缓冲区没满就不会进行实际 IO,所以读不到数据。
    两个办法:
    1. mysql 加参数,让它强制冲洗缓冲区
    2. 使用伪终端(pty),它默认是行缓冲的

    分享篇博客 深入理解子进程 : http://www.kkblog.me/notes/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3%E5%AD%90%E8%BF%9B%E7%A8%8B
    ouiki
        15
    ouiki  
    OP
       2017-08-28 14:59:33 +08:00
    @topbandit 大神留步。
    我改了一下我的代码。试了,不打印。求大神给看看。

    def stdout_theard(stdout_lock, p_stdout):
    for i in range(3000):
    s = p_stdout.readline()
    if len(s)>0:
    print s # 这里没有打印
    time.sleep(0.01)

    if __name__ == "__main__":
    os.chdir('C:\\Program Files (x86)\\MySQL\\MySQL Server 5.0\\bin')
    s_mian_command = 'mysql -h localhost -uroot -p1234'
    l_command = ['show databases;', 'use mysql;', 'show tables;']

    sub_process = subprocess.Popen(s_mian_command,
    stdin = subprocess.PIPE,
    stdout = subprocess.PIPE,
    stderr = subprocess.PIPE,
    shell = True)

    stdout_lock = threading.Lock()
    thread_read_output = threading.Thread(target=stdout_theard,
    args=(stdout_lock,sub_process.stdout))
    thread_read_output.setDaemon('True')
    thread_read_output.start()

    for s_command in l_command:
    time.sleep(1)
    sub_process.stdin.write(s_command + '\r\n')
    print s_command # 这里是打印的

    打印的结果就是:
    show databases;
    use mysql;
    show tables;

    密码啥的都没问题。后台也确确实实执行了的(我用 PowerCmd 能看到后台是执行了的),就是没打印。
    黑人问号黑人问号。
    llbgurs
        16
    llbgurs  
       2017-08-28 15:07:54 +08:00
    为什么没有 sub_process.p.communicate()
    ouiki
        17
    ouiki  
    OP
       2017-08-28 15:18:37 +08:00
    感谢大家一直在帮助我,怎奈我天资有限,一直没有解决。
    @llbgurs out,err = sub_process.communicate()的意思是线程结束后才返回 stdout 和 stderr 吧?
    我的程序不能停止后才返回,还有后续的动作。
    araraloren
        18
    araraloren  
       2017-08-28 15:39:15 +08:00
    @ouiki ...真是不可救药了,我都说了是 mysql 的问题,跟你的用法没有关系。。
    这就如同 程序本身没有输出 你还能 capture 到输出?
    topbandit
        19
    topbandit  
       2017-08-28 15:42:32 +08:00
    这里排版不太好,我在 OSC 写了段 https://my.oschina.net/u/3573498/blog/1524999
    llbgurs
        20
    llbgurs  
       2017-08-28 15:45:39 +08:00
    @ouiki mysql 不能这样执行吗? mysql -h localhost -uroot -p1234 -e "show databases"
    ouiki
        21
    ouiki  
    OP
       2017-08-28 15:56:49 +08:00
    @araraloren 谢谢,按你说的确实是有输出。赞啊~~

    接下来就是我的问题了,我以为用 mysql,大家都有这个环境,就能很好的说明这个问题。看来大能果然不能糊弄。
    我实际的问题和 mysql 类似,是叫“ cisco anyConnect ” 的一个程序( vpncli.exe )。
    流程是差不多,通过 vpncli.exe 登录⇒建立 VPN 连接⇒返回状态⇒访问测试页面⇒退出 vpncli.exe 。
    所以 mysql -n 参数我是学到了,但 vpncli.exe 没有-n 参数。
    araraloren
        22
    araraloren  
       2017-08-28 17:00:20 +08:00
    @ouiki
    那这个没有你说的那个选项的话不好说, 你可以考虑上面楼层说的伪终端
    不过看你是 win 下,具体情况那就不清楚具体支持不支持 WIN 不了
    不过还有一个办法就是你去和 cmd/shell 交互,而不是和你的应用程序交互。。
    ouiki
        23
    ouiki  
    OP
       2017-08-29 09:06:52 +08:00
    @araraloren 再次感谢。
    客户那边限制,只能使用 win。

    >>> 不过还有一个办法就是你去和 cmd/shell 交互,而不是和你的应用程序交互
    这是个思路。
    ouiki
        24
    ouiki  
    OP
       2017-08-29 09:07:27 +08:00
    @guyskk 感谢,那篇文章让我受益良多。
    lolizeppelin
        25
    lolizeppelin  
       2017-08-31 13:28:37 +08:00 via Android
    不要用 readline 老老实实 read
    lolizeppelin
        26
    lolizeppelin  
       2017-08-31 13:33:26 +08:00 via Android
    还有 不要只顾 stdout 有些软件不标准
    普通错误都往 stderr 里塞
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1144 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 33ms · UTC 22:57 · PVG 06:57 · LAX 14:57 · JFK 17:57
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.