如何在Python中调用外部命令,就像在shell或命令提示符中键入它一样?
如何在Python中调用外部命令,就像在shell或命令提示符中键入它一样?
还要检查一下Python库"pexpect"。
它可以交互式地控制外部程序/命令,甚至包括ssh、ftp、telnet等。你只需要输入类似于:
child = pexpect.spawn('ftp 192.168.0.24')
child.expect('(?i)name .*: ')
child.sendline('anonymous')
child.expect('(?i)password')
>>> subprocess.check_output(["ls", "-l", "/dev/null"])
'crw-rw-rw- 1 root root 1, 3 Oct 18 2007 /dev/null\n'
还要注意shell参数。
如果shell为True,则指定的命令将通过shell执行。如果您主要使用Python来增强大多数系统shell提供的控制流,并且仍然希望方便地访问其他shell功能(例如shell管道,文件名通配符,环境变量扩展和将~扩展到用户的主目录),则这可能非常有用。但是,请注意,Python本身提供了许多类似于shell的功能实现(特别是glob,fnmatch,os.walk(),os.path.expandvars(),os.path.expanduser()和shutil)。
subprocess.run
是建议使用的方法(自Python 3.5起),如果您的代码不需要与早期版本的Python保持兼容性。它更一致,并且提供类似Envoy的易用性。(尽管管道不是那么简单。请参见此问题以了解详情。)
以下是文档中的一些示例。
运行进程:
>>> subprocess.run(["ls", "-l"]) # Doesn't capture output
CompletedProcess(args=['ls', '-l'], returncode=0)
>>> subprocess.run("exit 1", shell=True, check=True)
Traceback (most recent call last):
...
subprocess.CalledProcessError: Command 'exit 1' returned non-zero exit status 1
捕获输出:
>>> subprocess.run(["ls", "-l", "/dev/null"], stdout=subprocess.PIPE)
CompletedProcess(args=['ls', '-l', '/dev/null'], returncode=0,
stdout=b'crw-rw-rw- 1 root root 1, 3 Jan 23 16:23 /dev/null\n')
我建议尝试 Envoy。它是 subprocess 的包装器,而 subprocess 又旨在取代较旧的模块和函数。Envoy 是面向人类的 subprocess。
来自README的示例用法:
>>> r = envoy.run('git config', data='data to pipe in', timeout=2)
>>> r.status_code
129
>>> r.std_out
'usage: git config [options]'
>>> r.std_err
''
>>> r = envoy.run('uptime | pbcopy')
>>> r.command
'pbcopy'
>>> r.status_code
0
>>> r.history
[<Response 'uptime'>]
这就是我运行命令的方式。 这段代码基本上包含了您所需的一切。
from subprocess import Popen, PIPE
cmd = "ls -l ~/"
p = Popen(cmd , shell=True, stdout=PIPE, stderr=PIPE)
out, err = p.communicate()
print "Return code: ", p.returncode
print out.rstrip(), err.rstrip()
如何从Python中执行程序或调用系统命令
简单来说,使用subprocess.run
即可,它会返回一个CompletedProcess
对象:
>>> from subprocess import run
>>> from shlex import split
>>> completed_process = run(split('python --version'))
Python 3.8.8
>>> completed_process
CompletedProcess(args=['python', '--version'], returncode=0)
run
函数想要一个词法解析后的shell参数列表 - 这就是你在shell中键入的内容,用空格分隔,但不包括被引号包含的空格,因此使用专门的函数split
将你在shell中真正键入的内容分割开。
从Python 3.5开始,文档建议使用subprocess.run:
调用子进程的推荐方法是对于所有可处理的用例都使用 run() 函数。对于更高级别的用例,可以直接使用底层的 Popen 接口。
以下是最简单用法的示例 - 它正好按照要求执行:
>>> from subprocess import run
>>> from shlex import split
>>> completed_process = run(split('python --version'))
Python 3.8.8
>>> completed_process
CompletedProcess(args=['python', '--version'], returncode=0)
run
等待命令成功完成,然后返回一个 CompletedProcess
对象。如果您指定了 timeout=
参数,它可能会引发 TimeoutExpired
,如果失败并且您传递了 check=True
,则可能会引发 CalledProcessError
。
正如您从上面的示例中推断出的那样,默认情况下,stdout 和 stderr 都会被导向到自己的 stdout 和 stderr。
我们可以检查返回的对象并查看给定的命令和返回码:
>>> completed_process.args
['python', '--version']
>>> completed_process.returncode
0
如果你想捕获输出,可以将subprocess.PIPE
传递给相应的stderr
或stdout
:
>>> from subprocess import PIPE
>>> completed_process = run(shlex.split('python --version'), stdout=PIPE, stderr=PIPE)
>>> completed_process.stdout
b'Python 3.8.8\n'
>>> completed_process.stderr
b''
这些属性返回字节。
你可以通过手动提供命令字符串(就像问题中建议的那样)来轻松地从提供程序上构建字符串。 不要通过编程方式构建字符串。 这会存在潜在的安全问题。最好假设您不能信任输入。
>>> import textwrap
>>> args = ['python', textwrap.__file__]
>>> cp = run(args, stdout=subprocess.PIPE)
>>> cp.stdout
b'Hello there.\n This is indented.\n'
请注意,只应按位置传递args
。
这是源代码中的实际签名,并且如help(run)
所示:
def run(*popenargs, input=None, timeout=None, check=False, **kwargs):
popenargs
和kwargs
会传递给Popen
构造函数。input
可以是一个字节字符串(或Unicode,如果指定了编码或universal_newlines=True
),该字符串将被管道发送到子进程的标准输入。timeout=
和check=True
的含义可在文档中找到:timeout
参数传递给Popen.communicate()
。如果超时,则子进程将被杀死并等待。在子进程终止后,将重新引发TimeoutExpired
异常。check
为True
,并且进程以非零退出代码退出,则会引发CalledProcessError
异常。该异常的属性保存了参数、退出代码以及如果捕获了它们则为stdout和stderr。check=True
更好理解:
>>> subprocess.run("exit 1", shell=True, check=True) Traceback (most recent call last): ... subprocess.CalledProcessError: Command 'exit 1' returned non-zero exit status 1
以下是文档中给出的扩展签名:
subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None,
shell=False, cwd=None, timeout=None, check=False, encoding=None,
errors=None)
请注意,这表示仅应按位置传递args列表。因此,请将其余参数作为关键字参数传递。
何时使用Popen
?我很难基于参数找到用例。然而,直接使用Popen
将使您访问其方法,包括poll
,'send_signal','terminate'和'wait'。
以下是源代码中给出的Popen
签名。 我认为这是最精确地封装信息的方式(而不是使用help(Popen)
):
def __init__(self, args, bufsize=-1, executable=None,
stdin=None, stdout=None, stderr=None,
preexec_fn=None, close_fds=True,
shell=False, cwd=None, env=None, universal_newlines=None,
startupinfo=None, creationflags=0,
restore_signals=True, start_new_session=False,
pass_fds=(), *, user=None, group=None, extra_groups=None,
encoding=None, errors=None, text=None, umask=-1, pipesize=-1):
但更有用的是Popen
文档:
subprocess.Popen(args, bufsize=-1, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=True, shell=False, cwd=None, env=None, universal_newlines=None, startupinfo=None, creationflags=0, restore_signals=True, start_new_session=False, pass_fds=(), *, group=None, extra_groups=None, user=None, umask=-1, encoding=None, errors=None, text=None)
Execute a child program in a new process. On POSIX, the class uses os.execvp()-like behavior to execute the child program. On Windows, the class uses the Windows CreateProcess() function. The arguments to Popen are as follows.
理解有关Popen
的其余文档将留给读者作为练习。
os.system(...)
的最大优点是简单易用。而subprocess
更好,尤其在Python 3.5及以后版本中使用仍然很容易。
import subprocess
subprocess.run("ls -a", shell=True)
注意: 这是你问题的确切答案 - 运行一个像在shell中一样的命令
像在shell中一样
如果可能的话,去掉Shell开销并直接运行命令(需要一个列表)。
import subprocess
subprocess.run(["help"])
subprocess.run(["ls", "-a"])
以列表形式传递程序参数。 不要为包含空格的参数包括\"
转义。
以下代码不言自明:
import subprocess
result = subprocess.run(["ls", "-a"], capture_output=True, text=True)
if "stackoverflow-logo.png" in result.stdout:
print("You're a fan!")
else:
print("You're not a fan?")
result.stdout
是正常程序输出,不包括错误信息。阅读result.stderr
以获得它们。
capture_output=True
- 打开捕获功能。否则result.stderr
和result.stdout
将为None
。自Python 3.7起可用。
text=True
- 是一个方便的参数,添加于Python 3.7,可将接收到的二进制数据转换为易于处理的Python字符串。
执行:
if result.returncode == 127: print("The program failed for some weird reason")
elif result.returncode == 0: print("The program succeeded")
else: print("The program failed unexpectedly")
result.check_returncode()
但这是Python,所以有一个更方便的参数check
,它可以自动为您执行相同的操作:
result = subprocess.run(..., check=True)
您可能希望将所有程序输出都放在标准输出中,包括错误。要实现这一点,请运行
result = subprocess.run(..., stderr=subprocess.STDOUT)
result.stderr
将变为None
,而result.stdout
将包含所有内容。
shell=False
需要一个列表参数。但是,您可以使用shlex自行拆分参数字符串。
import subprocess
import shlex
subprocess.run(shlex.split("ls -a"))
就是这样。
你可能刚开始使用Python时遇到了这个问题。让我们看一些常见问题。
FileNotFoundError: [Errno 2] 没有那个文件或目录: 'ls -a': 'ls -a'
你正在运行一个没有 shell=True
的子进程。要么使用列表 (["ls", "-a"]
),要么设置 shell=True
。
TypeError: [...] NoneType [...]
检查是否已设置 capture_output=True
。
TypeError: 需要一个类似字节的对象,而不是 [...]
您始终从程序接收字节结果。如果想像普通字符串一样处理它,请设置 text=True
。
subprocess.CalledProcessError: 命令 '[...]' 返回非零退出状态 1。
您的命令未成功运行。您可以禁用 returncode 检查或检查实际程序的有效性。
TypeError: init() got an unexpected keyword argument [...]
您可能正在使用早于 3.7.0 版本的 Python;请更新到最新版本。否则,此 Stack Overflow 帖子中还有其他答案显示您的旧版替代解决方案。
shell=True
对我有什么帮助。在 subprocess 中有哪些更好的东西呢? - reducing activityos.system(...)
在简单的“盲目”执行方面是一个合理的选择。然而,使用情况相当有限 - 一旦你想要捕获输出,你就必须使用另一个库,然后你开始在代码中使用两个类似的用例 - subprocess 和 os。我更喜欢保持代码整洁,只使用其中一个。其次,我本来会把这一部分放在最前面,但 TL;DR 必须准确回答问题,你不应该使用 shell=True
,而是使用我在“首选方式”部分中写的方法。 - famemanos.system(...)
和 shell=True
的问题在于,它会生成一个新的 shell 进程来执行你的命令。这意味着你必须手动进行转义,这并不像你想象的那么简单,特别是当你同时针对 POSIX 和 Windows 时。对于用户提供的输入,这是不可行的(想象一下用户输入了带有 "
引号的内容 - 你还需要对它们进行转义)。此外,shell 进程本身可能会加载你不需要的代码 - 它不仅会延迟程序,而且还可能导致意外的副作用,最终导致错误的返回代码。 - famemanos.system(...)
是可以使用的。但是,一旦你写的不仅仅是一个快速的Python辅助脚本,我建议你使用没有shell=True
的subprocess.run。关于os.system的缺点的更多信息,我想建议您阅读这个SO答案:https://dev59.com/P1cP5IYBdhLWcg3wNnjW#44731082 - famemanos.system
可以使用,但有点过时。 它也不是非常安全。 相反,请尝试使用 subprocess
。 subprocess
不直接调用 sh,因此比 os.system
更安全。
在这里获取更多信息。
subprocess
并不能消除所有安全问题,而且它本身也有一些讨厌的问题。 - tripleee还有Plumbum
>>> from plumbum import local
>>> ls = local["ls"]
>>> ls
LocalCommand(<LocalPath /bin/ls>)
>>> ls()
u'build.py\ndist\ndocs\nLICENSE\nplumbum\nREADME.rst\nsetup.py\ntests\ntodo.txt\n'
>>> notepad = local["c:\\windows\\notepad.exe"]
>>> notepad() # Notepad window pops up
u'' # Notepad window is closed by user, command returns
check_output
需要一个列表而不是一个字符串。如果您不依赖于引号空格来使您的调用有效,则最简单、最易读的方法是subprocess.check_output("ls -l /dev/null".split())
。 - Bruno Bronoskyshell=True
传递一个单个字符串,然后 shell 将负责解析和执行。在您提到的情况下使用纯.split()
是可以的,但是初学者通常不理解细微之处;您可能最好推荐shlex.split()
,它确实正确处理引号和反斜杠转义。 - tripleee