[求助] subprocess 的 stdout 堵塞问题 - V2EX
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 6967 次点击
    这是一个创建于 3045 天前的主题,其中的信息可能已经有所发展或是发生改变。
    要求是这样的:
    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     Solana     3318 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 23ms UTC 11:06 PVG 19:06 LAX 03:06 JFK 06:06
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86