subprocess模块还提供了很多方便的方法来使得执行 shell 命令

时间:2023-03-08 17:18:06
subprocess模块还提供了很多方便的方法来使得执行 shell 命令

现在你可以看到它正常地处理了转义。

注意

实际上你也可以在shell=False那里直接使用一个单独的字符串作为参数, 但是它必须是命令程序本身,这种做法和在一个列表中定义一个args没什么区别。而如果当shell=False时候直接执行字符串命令,则会报错:

>>> subprocess.Popen('echo "Hello world!"', shell=False)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python2.5/subprocess.py", line 594, in __init__
errread, errwrite)
File "/usr/lib/python2.5/subprocess.py", line 1147, in _execute_child
raise child_exception
OSError: [Errno 2] No such file or directory
如果我们还是坚持使用一个字符串,Python 会认为这个完整的字符串是一个可执行的程序名,而实际上没有一个叫做echo "Hello world!"的程序,所以报错了。正确的做法要用 list 分开传送参数。
检查 PATH 中的程序
这里有个方法可以找出程序真正的位置:
import os
def whereis(program):
for path in os.environ.get('PATH', '').split(':'):
if os.path.exists(os.path.join(path, program)) and \
not os.path.isdir(os.path.join(path, program)):
return os.path.join(path, program)
return None
让我们用它来找出echo程序在哪里:
>>> location = whereis('echo')
>>> if location is not None:
... print location
/bin/echo
这个方法同样可以检查用户的PATH里面是否有 Python 需要的程序。

当然你也可以使用命令行中的程序whereis来找出程序的路径。

$ whereis echo
echo: /bin/echo /usr/share/man/man1/echo.1.gz
注意

无论我们使用shell为True或者False, 我们都没有指定执行程序的全路径。 如果这个程序在上下文环境的PATH变量中,我们才可以执行。 当然如果你愿意,指定全路径也没问题。

你也可以坚持指定executable为想要执行的程序, 然后args就不设定程序。虽然没看到明确的文档,不过我电脑上面可以这么执行:

>>> subprocess.Popen(['1', '2', '3'], shell=False, executable='echo')
2 3
<subprocess.Popen object at 0xb776f56c>
不直接使用 shell 会导致不能直观地使用重定向、管道、here 文档、shell 参数或其他那些可以在命令行使用的技巧。接下来我们会看看怎么使用这些功能。
从标准输出和错误重定向
当你使用Popen执行程序时候,输出内容通常被发送到 stdout, 这也是为什么你能看到这些内容。

当你想尝试从某个程序读取标准输出信息时候,则需要在调用Popen之前设定stdout参数。要设定的值是subprocess.PIPE:

subprocess.PIPE

可以为Popen指定标准输入、标准输出和标准错误输出的参数, 需要注意的是标准输出流需要打开可写。

这里有个范例:

>>> process = subprocess.Popen(['echo', 'Hello World!'], shell=False, stdout=subprocess.PIPE)
To read the output from the pipe you use thecommunicate()method:

为了从管道获取输出,你可以使用communicate()方法:

>>> print process.communicate()
('Hello World!\n', None)
communicate()的返回值是一个 tuple,第一个值是标准输出的数据, 第二个输出是标准错误输出的内容。
这里有段脚本能让我们测试标准输出和标准错误输出的表现行为, 将它存为test1.py:
import sys
sys.stdout.write('Message to stdout\n')
sys.stderr.write('Message to stderr\n')
执行它:
>>> process = subprocess.Popen(['python', 'test1.py'], shell=False, stdout=subprocess.PIPE)
Message to stderr
>>> print process.communicate()
('Message to stdout\n', None)
注意标准错误输出在被生成后就打印了,而标准输出则被管道传输了。 这是因为我们只设定了标准输出的管道,让我们同时也设定标准错误输出。
>>> process = subprocess.Popen(['python', 'test1.py'], shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
>>> print process.communicate()
('Message to stdout\n', 'Message to stderr\n')
这次标准输出和标准错误输出都被 Python 获取到了。

现在所有的消息能被打印出来了,如果我们再次调用communicate(), 则会得到一个错误信息:

>>> print process.communicate()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python2.5/subprocess.py", line 668, in communicate
return self._communicate(input)
File "/usr/lib/python2.5/subprocess.py", line 1207, in _communicate
rlist, wlist, xlist = select.select(read_set, write_set, [])
ValueError: I/O operation on closed file
communicate()方法读取标准输出和标准错误输出时候,遇到结束符(EOF) 就会结束。
重定向 stderr 到 stdout
如果你想将错误信息重定向到标准输出,只需要给stderr参数指定一个特殊值:stderr=subprocess.STDOUT即可。
写入标准输入
写数据入一个进程和之前所述比较类似。为了要写入数据,需要先打开一个管道到标准输入。 通过设定Popen参数stdin=subproces.PIPE可以实现。
为了测试,让我们另外写一个仅输出Received:和输入数据的程序。 它在退出之前会输出消息。调用这个test2.py:
import sys
input = sys.stdin.read()
sys.stdout.write('Received: %s'%input)
为了发送消息到标准输入,把你想发送的信息作为communicate()的参数input。让我们跑起来:
>>> process = subprocess.Popen(['python', 'test2.py'], shell=False, stdin=subprocess.PIPE)
>>> print process.communicate('How are you?')
Received: How are you?(None, None)
注意test2.py发送的信息被打印到标准输出,随后的是(None, None), 这是因为标准输出和标准错误输出没有设定输出管道。

你可以和之前那样指定stdout=subprocess.PIPE和stderr=subprocess.PIPE来设定输出管道。

类文件属性
Popen拥有stdout和stderr属性,从而可以当作文件一样写出数据,同时stdin属性可以像文件一样读取数据。 你可以使用他们来替换communicate()。下面我们将看如何用它们。
读写同一个进程
这里有个例子,将它保存为test3.py:
import sys
while True:
input = sys.stdin.readline()
sys.stdout.write('Received: %s'%input)
sys.stdout.flush()
这个程序也是简单的响应接受到的数据,让我们把它跑起来:
>>> import time
>>> process = subprocess.Popen(['python', 'test3.py'], shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
>>> for i in range(5):
... process.stdin.write('%d\n' % i)
... output = process.stdout.readline()
... print output
... time.sleep(1)
...
Received: 0

Received: 1

Received: 2

Received: 3

Received: 4

>>>
每隔一秒钟会输出一行。

现在你应该掌握了所有需要通过 Python 来跟 Shell 交互需要的知识。

获取返回值,poll()和wait()
当一个程序退出时候,他会返回一个正整数来表明它的退出状态。 0 代表「成功地结束」,非零则表示「非正常结束」。 大部分系统要求返回值在 0-127 之间,其他都是未定义的结果。 一些系统会有事先定义好的错误对应关系,但一般不被拿出来用。 Unix 程序通常使用 2 作为命令语法错误,1 作为其他错误。
你可以通过Popen的.returncode属性获取程序返回值。这儿有个例子:
>>> process = subprocess.Popen(['echo', 'Hello world!'], shell=False)
>>> process.poll()
>>> print process.returncode
None
>>> process.poll()
0
>>> print process.returncode
0
这个returncode并不是一开始就设定好的,最初是默认值None, 它会一直是None知道你调用subprocess的方法比如poll()和wait()。 这些方法会设定returncode。因此,如果你想知道返回值,那就调用poll(2881064151)和wait()。

poll()和wait()方法区别很小:

Popen.poll(): 检查子进程是否结束。并设置和返回.returncode属性。Popen.wait(): 等待子进程结束。并设置和返回.returncode属性。

便捷的方法
subprocess模块还提供了很多方便的方法来使得执行 shell 命令更方便。 我没有全部试试。(译者:意思是让读者自己挖掘?)
理解sys.argv
如果你想写一个 Python 脚本来接受命令行参数, 那么命令行的参数会被传送并成参数sys.argv。 这里有个小范例,将它保存成command.py。
#!/usr/bin/env python
if __name__ == '__main__':
import sys
print "Executable: %s"%sys.argv[0]
for arg in sys.argv[1:]:
print "Arg: %s"%arg
if __name__ == '__main__'这行确保代码在被执行是才运行, 而不是被引入时候运行。给这个文件执行权限:
1
$ chmod 755 command.py
这里是一些运行时的范例:
$ python command.py
Executable: command.py
$ python command.py arg1
Executable: command.py
Arg: arg1
$ python command.py arg1 arg2
Executable: command.py
Arg: arg1
Arg: arg2
注意无论 Python 脚本怎么执行,sys.argv[0]始终是脚本的名称。sys.argv[1]和之后的参数是命令行接受的参数。 你可以通过使用参数-m来强制 Python 脚本作为模块导入使用。
$ python -m command
Executable: /home/james/Desktop/command.py
$ python -m command arg1
Executable: /home/james/Desktop/command.py
Arg: arg1
$ python -m command arg1 arg2
Executable: /home/james/Desktop/command.py
Arg: arg1
Arg: arg2
如你所见,Python 将-m作为命令的一部分,因此 `sys.srgv[0] 包含了脚本的全路径。 现在我们来直接执行它:
$ ./command.py
Executable: ./command.py
$ ./command.py arg1
Executable: ./command.py
Arg: arg1
$ ./command.py arg1 arg2
Executable: ./command.py
Arg: arg1
Arg: arg2
看吧,sys.argv[0]包含 Python 脚本的名称,sys.argv[1]以及他的兄弟们还是老样子,包含各类参数。
展开 Shell
有时候,我们会在 shell 中使用通配符来设定一组参数,比如, 我们在 Bash 中运行:
$ ./command.py *.txt
你可能觉得输出应该是:
Executable: ./command.py
Arg: *.txt
这不是你想要的结果。输出结果应该依赖当前文件夹中.txt文件的数目。执行效果如下:
Executable: ./command.py
Arg: errors.txt
Arg: new.txt
Arg: output.txt
Bash 会将\*.txt自动展开成所有符合.txt的参数。所以接受到的参数会超过你预期。

你可以通过将参数用引号抱起来来关闭 Shell 解释特性, 但是只要你用过,就会意识到在大多数情况下面这是非常有用的功能。

$ ./command.py "*.txt"
Executable: ./command.py
Arg: *.txt