python执行cmd subprocess持续_Python 执行系统命令 - subprocess 模块的使用

本文介绍了Python中调用系统命令的四种方式,重点讲解了subprocess模块的使用,包括基本用法、捕获命令输出、命令参数处理、输出重定向、错误处理和管道操作等。示例代码展示了如何安全地执行命令并捕获输出,同时对比了其他脚本语言如Perl、Groovy和Scala的相应操作。
摘要由CSDN通过智能技术生成

Python 可信手拈来写系统脚本,那么在 Python 中调用系统命令应该会比较便捷。所以本文来看看 Python 有几种方式调用系统命令,以及与回味一下其他几种脚本语言的类似操作。简单说来,Python 执行系统命令的方式有四种方式,即

os.system(cmd) (建议用 subprocess 模块)

os.popen(cmd) (Python 3 中还能用,但不推荐使用了)

commands 模块(在 Python 3 中已移除了该模块,基本是不必去了解它)

subprocess 模块(总是上面的矛头全指向它的,重点)

启动一个子进程来执行系统命令,可以获得标准输入,不能获到命令输出, 但可以得到一个状态码

1

2

3

importos

status_code=os.system('cat a.py | grep username')

# username = input("Username:")

它是调用 C 函数  system(), 命令直接输出到终端,它返回的错误状态码与直接执行命令的值可能不一样

建立一个到命令的管道,可以捕获到命令(shell) 执行后的输出

1

2

3

4

importos

out=os.popen("cat a.json | grep id")

print(out.read())

不知如何得到状态码,不建议使用了,推荐用 subprocess 模块

从 os.system(cmd) 和 os.popen(cmd) 的官方文档都引导我们去使用 subprocess 模块

关于使用 subprocess 模块,这儿有个很好的学习视频, 隔着墙的朋友们请忍着些日子。

关于 subprocess 模块的用法基本就是讲述 subprocess.run(...) 函数的用法,了解几个重要的参数。该方法的原型为

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

subprocess.run() 是在 Python 3.5 加入的,之前的版本要调用  subprocess.call() 方法。

基本用法

1

2

3

4

importsubprocess

p1=subprocess.run('ls')# 这行与终端执行 ls 效果一样,直接输出 ls 的结果

print('****\n',p1,p1.stdout)

scratch.py

scratch_1.py

****

CompletedProcess(args='ls', returncode=0) None

我们看到 p1 的类型是一个 CompletedProcess, 此时 p1.stdout 的内容是 None,也就是说我们没有捕获到命令的执行输出

捕获命令输出

1

2

p1=subprocess.run('ls',capture_output=True)

print('****\n',type(p1.stdout),p1.stdout)# p1.stdout.decode() 显示文本

加了 capture_output=True 参数,命令的执行结果不直接打印到控制台,而是可以通过 p1.stdout 获得的

****

b'scratch.py\nscratch_1.py\n'

capture_output=True 实质上会设置两个参数值 stdout=subprocess.PIPE, stderr=subprocess.PIPE,也就是它同时也会捕获标准错误输出,放置到  p1.stderr 中。如果只需捕获标准输出的话,只要 stdout=subprocess.PIPE.

默认时 p1.stdout 是一个bytes 类型,要显示为文本需要用 p1.stdout.decode(),或者再加上参数 text=True 就无需 stdout.decode() 了。用 encoding="utf-8" 也能免除 decode()。

1

2

p1=subprocess.run('ls',capture_output=True,text=True)

print('****\n',type(p1.stdout),'\n',p1.stdout)

****

scratch.py

scratch_1.py

加了 text=True 后,p1.stdout 的类型也变成了字符串 str。

命令参数与 shell 命令

前面的命令 ls 没有带参数,那怎么执行带参数的命令呢?先尝试着把命令与参数写在一块

1

subprocess.run('ls -la')

出错了

FileNotFoundError: [Errno 2] No such file or directory: 'ls -la': 'ls -la'

subprocess.run() 并不知道 ls -la 中的 ls 是命令,其余为参数,而是把整个字符串包括中间的空间当作命令,所以提示找不到 ls -la 这样的命令。解决方法有一,加上 shell=True 参数

1

subprocess.run('ls -la',shell=True)

total 16

drwxr-xr-x 4 yanbin 2022363315 128 Aug 12 21:17 .

drwxr-xr-x 11 yanbin 2022363315 352 Aug 12 20:49 ..

-rw-r--r-- 1 yanbin 2022363315 933 Aug 12 09:59 scratch.py

-rw-r--r-- 1 yanbin 2022363315 56 Aug 12 21:17 scratch_1.py

有了 shell=True 的话,run() 的第一个参数可以写的更复杂,管道操作也行,如下

1

subprocess.run('ls -la; pwd && ls | grep scratch',shell=True)

但 shell=True 不那么安全,比如说对于 ls 命令,它后面的目录部分是用户输入的话就可被注入

1

subprocess.run(f'ls -la ${folder}',shell=True)

假如用户输入的 folder="; rm -rf /, 就能随意的干活了。当 shell=True 是可以借助于 shlex.quote() 函数来抵御不安全的输入。

为安全起见,我们可保持默认的 shell=False 值,命令参数则必须以列表形式提供

1

subprocess.run(['ls','-la',folder])

列表第一个元素为命令,其他为参数,这时候 folder 能够防御住攻击了。

输出重定向(文件或设备)

1

2

withopen('output.txt','w')asfile:

subprocess.run(['ls','-la'],stdout=file,text=True)

现在命令的输出进到文件 output.txt 中了。要让标准错误输出也重定向到文件的话,就加上 stderr=file,或者 stdout=file, stderr=subprocess.STDOUT。这一连贯的操作就好比执行如下的 shell  命令

1

2

3

4

5

6

withopen('output.txt','w')asfile:

p1=subprocess.run(['ls','-la','lala'],stdout=file,stderr=subprocess.STDOUTtext=True)

print(p1.returncode)

# 上面的代码效果与这个 shell 命令差不多, 2 为标准错误输出,1 为标准输出

ls-lalala>output.txt2>&1

由于 lala 目录不存在,所以 p1.returncode 为 1, 非零即为有错误,看看  output.txt 中的内容为

ls: abc: No such file or directory

如果想忽略错误输出,例如一个程序中既有标准输出又有标准错误输出,且返回状态码为 2,用下面 Python 程序来模拟

1

2

3

4

5

6

7

#!/usr/bin/env python3

importsys

print('hello stdout')

print('hello stderr',file=sys.stderr)

exit(2)

想要执行出下方第四行中命令的效果

1

2

3

4

5

6

7

8

$./test.py

hellostdout

hellostderr

$./test.py>output.txt2>/dev/null

$echo$?

2

$catoutput.txt

hellostdout

相应的 subprocess.run() 代码如下

1

2

3

4

withopen('output.txt','w')asfile:

p1=subprocess.run('./test.py',stdout=file,text=True,

stderr=subprocess.DEVNULL)

print(p1.returncode)# 这时候的 p1.stderr 就是 None

报告错误

如果我们捕获了输出后,subprocess.run() 执行命令出错时不会报告给  Python 解释器

1

p1=subprocess.run(['ls','-la','lala'],capture_output=True,text=True)

lala 目录不存在,但上面的代码会没有任何输出,我们只得在执行后检查 p1 的返回码及 p1.stderr 的值

1

2

ifp1.returncode!=0:

print(p1.stderr)

输出

ls: lala: No such file or directory

要是加上 check=True 参数就不一样的了

1

subprocess.run(['ls','-la','lala'],capture_output=True,text=True,check=True)

直接报告异常

Traceback (most recent call last):

File "/Users/yanbin/Library/Preferences/PyCharmCE2019.1/scratches/scratch_1.py", line 4, in

p1 = subprocess.run(['ls', '-la', 'lala'], capture_output=True, text=True, check=True)

File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/subprocess.py", line 487, in run

output=stdout, stderr=stderr)

subprocess.CalledProcessError: Command '['ls', '-la', 'lala']' returned non-zero exit status 1.

这对于希望在有任何命令执行异常时退出很有用

管道操作

前面提到过,用 subprocess.run() 设置 shell=True 时,命令中可以随意使用复杂的 shell 命令,包括管道操作。如果 shell=False 默认值时,管道操作就需要显式的执行多个 subprocess.run() 操作并且通过像 input=p1.stdout 这样串联起来,这原本就是管道的意义

1

2

3

p1=subprocess.run(['cat','a.json'],capture_output=True,text=True)

p2=subprocess.run(['grep','id'],capture_output=True,text=True,input=p1.stdout)

print(p2.stdout)

相当于执行了 shell  命令

1

cata.json|grepid

其他

encoding 和 errors 参数是 Python 3.6 加入的。encoding='utf-8' 与 text=True 有着类似的功效

text, universal_newlines 和  capture_output 是 Python 3.7 加入的,之前版本捕获输出就要用 stdout=subprocess.PIPE, 且得到的 stdout 是 bytes 类型,stderr 相类似。

timeout= 会在多少秒之后还没执行完命令后得到 TimeoutExpired 异常,并且终止掉该命令的执行

其他脚本语言参考

学习过了 Python 调用系统命令之后不妨顺道体验一下 Perl, Groovy, Scala 怎么操作的

Perl 执行系统命令

1

2

3

4

5

6

7

$out=`ls-l|grephttp`;# 捕获输出到 $out 变量,不会输出到控制台

print$out;

$code=system('ls -l | grep http');# 直接输出到控制台,返回值只是一个状态码

print$code;

exec('ls -l | grep http');# 直接输出到控制台,没有返回值

Perl 执行的是 shell 命令,所以直接支持管道操作

Groovy 执行系统命令

1

2

3

4

5

defp1='ls -l'.execute().text//不能直接执行 'ls -l | grep http'

print(p1)

defp2='ls -l'.execute()|'grep http'.execute()//管道操作

print(p2.text)

Scala 执行系统命令

1

2

3

4

5

6

7

8

importscala.sys.process._

importscala.language.postfixOps

valcode="ls -l"!//直接输出到控制台,返回值为状态码

valout="ls -l"!!//返回值为命令的输出

valcode="ls -l"#| "grep http" !  //管道操作

valout1="ls -l"#| "grep http" !! //管道操作

管道操作要用 #| 操作符,不能在一个字符串中用管道连接多个命令,这不是一个 shell

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值