[求助] subprocess 的 stdout 堵塞问题

2017-08-28 08:25:41 +08:00
 ouiki
要求是这样的:
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 反复试,导致锁死。
6186 次点击
所在节点    Python
26 条回复
21grams
2017-08-28 09:54:22 +08:00
没成功具体是什么问题?
ouiki
2017-08-28 10:08:51 +08:00
放到线程里仍旧是阻塞。
重定向到文件里,写不进去。文件总是空,只有强制退出( Ctrl+C )后,才能写到文件里,看来也是阻塞的问题。

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

前边没仔细看你的需求,如果仅仅是 show databases 的输出,output 多大,会引起 pipe 阻塞?
topbandit
2017-08-28 11:14:50 +08:00
前面多余,你用一条 mysql 命令搞好了
topbandit
2017-08-28 11:18:44 +08:00
mysql -u -p -e
此帖终结
guyskk
2017-08-28 13:11:14 +08:00
缓冲 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
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
2017-08-28 15:07:54 +08:00
为什么没有 sub_process.p.communicate()
ouiki
2017-08-28 15:18:37 +08:00
感谢大家一直在帮助我,怎奈我天资有限,一直没有解决。
@llbgurs out,err = sub_process.communicate()的意思是线程结束后才返回 stdout 和 stderr 吧?
我的程序不能停止后才返回,还有后续的动作。
araraloren
2017-08-28 15:39:15 +08:00
@ouiki ...真是不可救药了,我都说了是 mysql 的问题,跟你的用法没有关系。。
这就如同 程序本身没有输出 你还能 capture 到输出?
topbandit
2017-08-28 15:42:32 +08:00
这里排版不太好,我在 OSC 写了段 https://my.oschina.net/u/3573498/blog/1524999
llbgurs
2017-08-28 15:45:39 +08:00
@ouiki mysql 不能这样执行吗? mysql -h localhost -uroot -p1234 -e "show databases"

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/386235

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX