当前位置:   article > 正文

subprocess模块教程

subprocess

Python的subprocess模块,用来创建和管理子进程(不是线程),并能够与创建的子进程的stdin,stdout,stderr连接通信,获取子进程执行结束后的返回码,在执行超时或执行错误时得到异常。

subprocess模块,用来取代几个老的函数接口,包括:

  1. # subprocess replacement:
  2. os.system
  3. os.spawn* # os.spawn* means spawn family funtions

以上创建子进程的老接口,就不要再使用了。

从Python3.5版本开始,subprocess模块内部又进行了一次整合 ,最后就剩下官方推荐的两个接口函数,分别是:

  1. subprocess.run()
  2. subprocess.Popen()

run() 函数的使用场景更多,它的底层调用的是Popen函数,Popen函数更灵活,适合更复杂的场景。我们只要好好学习这两个函数接口的使用,就能够掌握subprocess模块的几乎所有功能。

引入subprocess模块

由于subprocess这个名字很长,考虑到这个模块对外接口的函数和对象名称都比较特别,本文就这样来引入吧:

  1. >>> from subprocess import *
  2. >>> dir()
  3. ['CalledProcessError', 'CompletedProcess', 'DEVNULL', 'PIPE', 'Popen', 'STDOUT', 'SubprocessError', 'TimeoutExpired', '__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'call', 'check_call', 'check_output', 'getoutput', 'getstatusoutput', 'run']

call,check_call,check_output,getoutput,getstatusoutput这些函数,都被run函数代替了,它们在存在只是为了保持向下兼容。从上面的打印还可以看出,subprocess这个模块提供的接口并不多。

subprocess.run() 函数的使用

如前文介绍,从Python3.5开始,出现了run函数,用来代替之前版本的一些函数接口。run函数的作用是:执行args参数所表示的命令,等待命令执行完毕,返回一个CompletedProcess对象。注意,run函数是同步函数,要等待!

run()函数的接口参数:

  1. subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None,
  2. capture_output=False, shell=False, cwd=None, timeout=None,
  3. check=False, encoding=None, errors=None, text=None, env=None,
  4. universal_newlines=None)

args参数,就是要通过创建进程而执行的命令及参数,run函数通过args来创建一个进程并执行。

shell参数,表示是否通过shell来执行命令(Linux下默认为/bin/sh),默认是False,这时args只能是一个不带参数的命令字符串,或者是命令和参数组成的一个list,如果shell=True,args就可以是一个我们常见的命令字符串。下面举例:

  1. >>> run('ls')
  2. acme.sh domain.key git-2.9.5.tar.gz lina Python-3.7.1
  3. teapot test.sh dataset git-2.9.5 lamp private.key
  4. Python-3.7.1.tgz test
  5. CompletedProcess(args='ls', returncode=0)
  6. >>> run(['ls','-lh'])
  7. total 28M
  8. drwxrwxr-x. 6 xinlin xinlin 130 Apr 5 16:56 acme.sh
  9. drwxrwxr-x. 5 xinlin xinlin 101 Jan 12 10:37 dataset
  10. -rw-rw-r--. 1 xinlin xinlin 3.2K Apr 7 13:45 domain.key
  11. drwxrwxr-x. 23 xinlin xinlin 20K Dec 29 2018 git-2.9.5
  12. -rw-rw-r--. 1 xinlin xinlin 5.7M Dec 28 2018 git-2.9.5.tar.gz
  13. drwxrwxr-x. 3 xinlin xinlin 108 May 31 22:12 lamp
  14. drwxrwxr-x. 3 xinlin xinlin 159 Feb 23 15:27 lina
  15. -rw-rw-r--. 1 xinlin xinlin 3.2K Apr 7 13:42 private.key
  16. drwxr-xr-x. 19 xinlin xinlin 4.0K Dec 28 2018 Python-3.7.1
  17. -rw-rw-r--. 1 xinlin xinlin 22M Dec 28 2018 Python-3.7.1.tgz
  18. drwxrwxr-x. 5 xinlin xinlin 4.0K May 31 22:33 teapot
  19. drwxrwxr-x. 2 xinlin xinlin 22 Jun 21 20:22 test
  20. -rw-rw-r--. 1 xinlin xinlin 48 Apr 6 15:23 test.sh
  21. CompletedProcess(args=['ls', '-lh'], returncode=0)
  22. >>> run('ls -lh', shell=True)
  23. total 28M
  24. drwxrwxr-x. 6 xinlin xinlin 130 Apr 5 16:56 acme.sh
  25. drwxrwxr-x. 5 xinlin xinlin 101 Jan 12 10:37 dataset
  26. -rw-rw-r--. 1 xinlin xinlin 3.2K Apr 7 13:45 domain.key
  27. drwxrwxr-x. 23 xinlin xinlin 20K Dec 29 2018 git-2.9.5
  28. -rw-rw-r--. 1 xinlin xinlin 5.7M Dec 28 2018 git-2.9.5.tar.gz
  29. drwxrwxr-x. 3 xinlin xinlin 108 May 31 22:12 lamp
  30. drwxrwxr-x. 3 xinlin xinlin 159 Feb 23 15:27 lina
  31. -rw-rw-r--. 1 xinlin xinlin 3.2K Apr 7 13:42 private.key
  32. drwxr-xr-x. 19 xinlin xinlin 4.0K Dec 28 2018 Python-3.7.1
  33. -rw-rw-r--. 1 xinlin xinlin 22M Dec 28 2018 Python-3.7.1.tgz
  34. drwxrwxr-x. 5 xinlin xinlin 4.0K May 31 22:33 teapot
  35. drwxrwxr-x. 2 xinlin xinlin 22 Jun 21 20:22 test
  36. -rw-rw-r--. 1 xinlin xinlin 48 Apr 6 15:23 test.sh
  37. CompletedProcess(args='ls -lh', returncode=0)

第1,6,22行,分别代表了3中不同的args参数的使用方式。

注意run函数返回的CompletedProcess对象,里面包含了args,以及命令执行的返回码。下面的代码示例,说明了访问CompletedProcess对象的方式。

  1. >>> proc = run('ls')
  2. Desktop Downloads Music Public test
  3. Documents examples.desktop Pictures Templates Videos
  4. >>> proc.args
  5. 'ls'
  6. >>> proc.returncode
  7. 0

CompletedProcess对象还可能包含更多的数据,请注意后面的代码示例。

stdin参数,指定命令的输入途径;

stdout参数,指定命令的输出途径;默认为None,如上面的代码示例,输出就直接打印出来了;

stderr参数,指定命令的error输出途径;

input参数,命令的具体输入内容,默认None,表示没有输入。input与stdin不能同时使用。先看一个有input参数的例子:

  1. >>> proc = run('grep fs',shell=True,input=b'adfs\ncccc\nfsfsf')
  2. adfs
  3. fsfsf
  4. >>> proc
  5. CompletedProcess(args='grep fs', returncode=0)
  6. >>> proc = run('grep fs',shell=True,input=b'adfs\ncccc\nfsfsf',stdout=PIPE)
  7. >>> proc
  8. CompletedProcess(args='grep fs', returncode=0, stdout=b'adfs\nfsfsf\n')
  9. >>> proc.stdout
  10. b'adfs\nfsfsf\n'

input默认是一个bytes流。

stdout=PIPE,表示将stdout重定向到管道,用了这个参数,grep fs命令的结果,就不会直接打印出来,而是存入了proc.stdout这个管道内。

下面的例子用到了stderr:

  1. >>> proc = run('ls fs',shell=True,stdout=PIPE,stderr=PIPE)
  2. >>> proc.stdout
  3. b''
  4. >>> proc.stderr
  5. b"ls: cannot access 'fs': No such file or directory\n"

看一个stdout与input配合起来使用的例子,有点像我们在Linux shell输入的有管道的命令行:

  1. >>> proc = run('grep fs',shell=True,input=b'adfs\ncccc\nfsfsf',stdout=PIPE)
  2. >>> run('cat -n',shell=True, input=proc.stdout)
  3. 1 adfs
  4. 2 fsfsf
  5. CompletedProcess(args='cat -n', returncode=0)

下面是使用stdin的代码例子,stdin的来源是一个文件:

  1. >>> f = open('tt.t','r')
  2. >>> proc = run('cat -n', shell=True, stdin=f)
  3. 1 12345
  4. 2 abcde
  5. 3 xyz..
  6. >>> f.close()

有一个在命令行常见的用法,就是把stderr重定向到stdout,如下:

  1. >>> proc = run('ls kk', shell=True, stdout=PIPE, stderr=STDOUT)
  2. >>> proc.stdout
  3. b"ls: cannot access 'kk': No such file or directory\n"

capture_output参数,这个参数顾名思义就是捕获进程的输出,stdout和stderr。capture_output=True的效果与设置stdout=PIPE, stderr=PIPE一样。设置了capture_output=True,就不能再设置stdout和stderr:

  1. >>> proc = run('ls kk', shell=True, capture_output=True)
  2. >>> proc
  3. CompletedProcess(args='ls kk', returncode=2, stdout=b'', stderr=b"ls: cannot access 'kk': No such file or directory\n")
  4. >>> proc.stdout
  5. b''
  6. >>> proc.stderr
  7. b"ls: cannot access 'kk': No such file or directory\n"

使用capture_output=True,只是让代码书写上更简单更短一些。

cwd参数,这个参数指示了当前工作路径。

  1. >>> proc = run('ls -lh', shell=True, cwd='/usr/local')
  2. total 36K
  3. drwxr-xr-x 2 root root 4.0K Feb 9 16:12 bin
  4. drwxr-xr-x 2 root root 4.0K Feb 9 16:12 etc
  5. drwxr-xr-x 2 root root 4.0K Feb 9 16:12 games
  6. drwxr-xr-x 2 root root 4.0K Feb 9 16:12 include
  7. drwxr-xr-x 3 root root 4.0K Jun 28 21:54 lib
  8. lrwxrwxrwx 1 root root 9 Jun 28 21:32 man -> share/man
  9. drwxr-xr-x 6 root root 4.0K Jun 28 23:34 python-3.7.3
  10. drwxr-xr-x 2 root root 4.0K Feb 9 16:12 sbin
  11. drwxr-xr-x 6 root root 4.0K Feb 9 16:15 share
  12. drwxr-xr-x 2 root root 4.0K Feb 9 16:12 src

text参数,universal_newlines参数,这两个参数的作用是一样的,universal_newlines这个参数的存在也是为了向下兼容(Python3.7开始有text参数,3.5和3.6都是universal_newlines参数),因此我们使用text就好了。text参数的作用是,将stdin,stdout,stderr修改为string模式。注意看上面的示例代码,都是bytes流。

  1. >>> run('grep fs', shell=True, input=b'asdfs\nfdfs', capture_output=True)
  2. CompletedProcess(args='grep fs', returncode=0, stdout=b'asdfs\nfdfs\n', stderr=b'')
  3. >>> run('grep fs', shell=True, input='asdfs\nfdfs', capture_output=True, text=True)
  4. CompletedProcess(args='grep fs', returncode=0, stdout='asdfs\nfdfs\n', stderr='')

timeout参数,设置进程执行的超时时间。如果时间到子进程还未结束, subprocess.TimeoutExpired异常会抛出。timeout参数的单位是秒。

  1. >>> try:
  2. ... run('python3', shell=True, input=b'import time;time.sleep(30)', timeout=1)
  3. ... except TimeoutExpired:
  4. ... print('timeout happened...')
  5. ...
  6. timeout happened...

以上代码,就是sleep 30秒,run函数设置timeout为1秒,触发subprocess.TimeoutExpired后,打印一点信息出来。

check参数,如果check=True,在子进程的返回不为0的时候,抛出subprocess.CalledProcessError异常。这时,run函数返回的CompletedProcess对象的returncode不可用。

  1. >>> try:
  2. ... proc = run('ls kk', shell=True, check=True, stderr=PIPE)
  3. ... except CalledProcessError:
  4. ... print(proc.returncode)
  5. ...
  6. 0

上面这段代码,走到了except里面,因为kk目录不存在,但是打印出来的returncode却是0,run函数没有成功返回,而是抛出异常,因此返回值不可用。

subprocess.Popen()函数的使用

run函数的底层,就是Popen函数。run函数是同步的,要等待子进程实行结束,或者超时。Popen创建子进程后,采用异步的方式,不会等待,要通过poll函数来判断子进程是否执行完毕。整理来说,Popen比run要更加灵活,如果run函数还不能满足你的需求,就考虑Popen吧。

Popen()函数的接口参数:

  1. Popen(args, bufsize=-1, executable=None, stdin=None, stdout=None, stderr=None,
  2. preexec_fn=None, close_fds=True, shell=False, cwd=None, env=None,
  3. universal_newlines=None, startupinfo=None, creationflags=0,
  4. restore_signals=True, start_new_session=False, pass_fds=(), *,
  5. encoding=None, errors=None, text=None)

参数args,stdin,stdout,stderr,shell,cwd,universal_newlines,text与run函数的含义和用法都是一样的。

Popen函数的基本用法:

  1. >>> proc = Popen('ls -hl', shell=True, stdout=PIPE, stderr=STDOUT)
  2. >>> out, _ = proc.communicate()
  3. >>> print(out.decode())
  4. total 37M
  5. -rw-r--r-- 1 xinlin xinlin 535 Jun 29 06:03 apache_log_reader.py
  6. -rw-r--r-- 1 xinlin xinlin 3.2M Jun 30 02:55 py.maixj.sql
  7. -rw-r--r-- 1 xinlin xinlin 3.2M Jun 29 19:20 py.online.sql
  8. drwxr-xr-x 19 xinlin xinlin 4.0K Jun 28 23:24 Python-3.7.3
  9. -rw-r--r-- 1 xinlin xinlin 22M Mar 25 13:59 Python-3.7.3.tgz
  10. -rw-r--r-- 1 xinlin xinlin 27 Jul 5 01:05 sleep.py
  11. -rw-r--r-- 1 xinlin xinlin 18 Jul 5 00:10 tt.t
  12. -rw-r--r-- 1 xinlin xinlin 800 Jun 29 03:26 walktree.py
  13. -rw-r--r-- 1 xinlin xinlin 8.2M Jun 29 05:47 www.access_log_2019_06_28
  14. >>> proc.returncode
  15. 0
  16. >>> proc.pid
  17. 2985

Popen函数以异步的方式创建一个子进程,返回一个Popen对象。我们通过 communicate 函数来获取stdout和stderr。communicate函数返回一个tuple,以上示例是将stderr=STDOUT,因此使用 _ 来表示为空的stderr。

Popen对象的communicate函数有两个参数,input和timeout,分别用来设置给子进程的输入和超时时间。有timeout参数,表示communicate函数会等待子进程执行结束,或者超时。

  1. >>> proc = Popen('grep fs', shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE)
  2. >>> out, err = proc.communicate(b'adfs\nfsmnjkl')
  3. >>> out
  4. b'adfs\nfsmnjkl\n'
  5. >>> err
  6. b''

再来一个有timeout的例子:

  1. >>> proc = Popen('python3', shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE)
  2. >>> try:
  3. ... out,err=proc.communicate(b'import time;time.sleep(30)', 1)
  4. ... except TimeoutExpired:
  5. ... print('time out...')
  6. ...
  7. time out...

Popen对象有一个 wait 成员函数,也可以设置一个timeout来等待子进程的结束:

  1. >>> try:
  2. ... proc=Popen('python3 -c "import time;time.sleep(30)"',shell=True,stdout=PIPE)
  3. ... returncode = proc.wait(15)
  4. ... except TimeoutExpired:
  5. ... print('after waiting 15 seconds, timeout finally...')
  6. ...
  7. after waiting 15 seconds, timeout finally...

注意对returncode的赋值,如果timeout发生,returncode就是not defined。当然也可以通过proc.returncode来获取。如果异常,proc.returncode的值是None。

很多时候,我们确定子进程会执行结束,只是无法确定需要的时间长度,这种情况就要用 poll 函数来判断子进程的执行是否结束:

  1. >>> def test_Popen():
  2. ... import time
  3. ... proc=Popen('python3 -c "import time;time.sleep(10)"',shell=True,stdout=PIPE)
  4. ... i = 0
  5. ... while True:
  6. ... returncode = proc.poll()
  7. ... if returncode is None:
  8. ... time.sleep(2)
  9. ... i += 2
  10. ... print('sleep',i,'seconds')
  11. ... continue
  12. ... else:
  13. ... print('sub process is terminated with returncode',returncode)
  14. ... break
  15. ...
  16. >>> test_Popen()
  17. sleep 2 seconds
  18. sleep 4 seconds
  19. sleep 6 seconds
  20. sleep 8 seconds
  21. sleep 10 seconds
  22. sleep 12 seconds
  23. sub process is terminated with returncode 0

Popen对象还有下列几个成员函数,以后有机缘时,再上示例代码吧:

  1. Popen.send_signal(signal)
  2. Popen.terminate()
  3. Popen.kill()

关于subprocess模块使用的更多信息,请直接参考官方的说明:https://docs.python.org/3/library/subprocess.html

-- EOF --

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/548414
推荐阅读
相关标签
  

闽ICP备14008679号