1. 博客/

python脚本中调用shell的几种方法

·1177 字·6 分钟
Python
方法一
#

1. os.system(‘cmd’)方法

使用system方法可以返回运行cmd命令的状态返回值,同时会在终端输出运行结果,但无法将执行的结果保存起来

system方法比较适用于执行单个命令、通常没有输出结果的情况

  • python交互模式下
>>> os.system('tar cvf  /data/1.tar /data/docker')
tar: Removing leading `/' from member names
/data/docker/
/data/docker/auth/
/data/docker/auth/htpasswd
/data/docker/a.sh
0                                                                 <-- 命令执行状态返回值
  • python脚本中
$ cat sh-system.py
#!/usr/bin/python36
import os,sys
if len(sys.argv) < 3 :
        print ('2 arguments is needed')
        sys.exit(1)
os.system('./test.sh %s %s'%(sys.argv[1],sys.argv[2]))            <--自动打印cmd在linux上执行的信息无需使用print
#os.system('./test.sh '+sys.argv[1]+' '+sys.argv[2])
$ cat test.sh
#!/usr/bin/bash
echo -e "This is a shell script. \nNAME:$1\tAUTHOR:$2"

运行python脚本,终端结果如下

[root@master python_learning]# ./sh-system.py test tom
This is a shell script. 
NAME:test       AUTHOR:tom
方法二
#

2. commands.getstatusoutput(‘cmd1;cmd2;…’)方法

commands.getstatusoutput方法可以取得命令的输出(包括标准和错误输出)和执行状态位

python3已经使用subprocess取代

  • python2交互式模式下
[root@master python_learning]# python
Python 2.7.5 (default, Aug  4 2017, 00:39:18) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-16)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import os,commands
>>> status,result=commands.getstatusoutput('ls')
>>> status
0
>>> result
'getIP.py\nsh-commands.py\nsh-popen.py\nsh-subprocess.py\nsh-system.py\ntest.sh\nzipdir\nzip.py'
>>> commands.getoutput('ls')                                    <--只返回命令执行结果
'getIP.py\nsh-commands.py\nsh-popen.py\nsh-subprocess.py\nsh-system.py\ntest.sh\nzipdir\nzip.py'
  • python脚本中
[root@master python_learning]# cat ./sh-commands.py
#!/usr/bin/python
import os,commands
status,result=commands.getstatusoutput('ls;cat /data/1.txt')
print (status)
print (result)

运行python脚本,终端结果如下

[root@master python_learning]# ./sh-commands.py   
256                                                                 <--cmd1;cmd2;...中有一个cmd执行失败,状态返回码就不为0
sh-commands.py
sh-system.py
test.sh
cat: /data/1.txt: No such file or directory
方法三
#

3.os.popen(‘cmd’)方法

popen(command [, mode=‘r’ [, bufsize]])

-> pipe Open a pipe to/from a command returning a file object. 返回一个类文件对象,调用该对象的read()或readlines()方法可以读取输出内容

command – 使用的命令

mode – 模式权限可以是 ‘r’(默认) 或 ‘w’

bufsize – 指明了文件需要的缓冲大小:0意味着无缓冲;1意味着行缓冲;其它正值表示使用参数大小的缓冲(大概值,以字节为单位)。负的bufsize意味着使用系统的默认值,一般来说,对于tty设备,它是行缓冲;对于其它文件,它是全缓冲。如果没有改参数,使用系统的默认值

  • python脚本中
[root@master python_learning]# vim sh-popen.py 
#!/usr/bin/env python36
#-*- coding:utf-8 -*-
import os,sys
if len(sys.argv) < 3 :
        print ('2 arguments is needed')
        sys.exit(1)
cmd = './test.sh %s %s'%(sys.argv[1],sys.argv[2])
print (os.popen(cmd).readlines())                                 <--返回的是类文件对象需要调用print打印出来
print (os.popen(cmd).read(),end='')

运行python脚本,终端结果如下

[root@master python_learning]# ./sh-popen.py test tom
['This is a shell script. \n', 'NAME:test\tAUTHOR:tom\n']
This is a shell script. 
NAME:test       AUTHOR:tom
方法四
#

4.subprocess模块

subprocess被用来替换一些老的模块和函数,如:os.system、os.spawn*、os.popen*、popen2.、commands.。所以强烈推荐使用subprocess模块。

subprocess模块中只定义了一个类: Popen。可以使用Popen来创建进程,并与进程进行复杂的交互。它的构造函数如下:

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

各参数请参考 Python Documentation中的定义

常用的几个函数subprocess.call()、subprocess.check_call()、subprocess.check_output()、subprocess.Popen()。实际上,上面的几个函数都是基于Popen()的封装(wrapper)。这些封装的目的在于让我们容易使用子进程。

  • subprocess.call()

subprocess.call(args,* , stdin=None, stdout=None, stderr=None, shell=False, cwd=None, timeout=None)

Run the command described by args. Wait for command to complete, then return the returncode attribute

运行命令,父进程等待子进程完成(阻塞),然后返回returncode(状态码)

[root@master python_learning]# python36
Python 3.6.3 (default, Jan  4 2018, 16:40:53) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-16)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import subprocess
>>> subprocess.call(['ls','-l','/data'])             <--args序列化的表达方式
total 20
-rw-r--r--. 1 root root 10240 Feb  8 11:06 1.tar
-rw-r--r--. 1 root root    24 Feb 22 14:56 1.txt
drwxr-xr-x. 2 root root    79 Dec 25 09:56 build_images
drwxr-xr-x. 2 root root     6 Jan 15 16:05 container
drwxr-xr-x. 4 root root    30 Jan 10 15:47 dcos
drwxr-xr-x. 3 root root    28 Feb  8 11:06 docker
drwxr-xr-x. 3 root root  4096 Feb 22 15:31 python_learning
drwxr-xr-x. 5 root root    36 Dec 11 11:07 zookeeper
0
>>> subprocess.call('ls -l /data',shell=True)        <--args字符串表达,指定shell=True
total 20
-rw-r--r--. 1 root root 10240 Feb  8 11:06 1.tar
-rw-r--r--. 1 root root    24 Feb 22 14:56 1.txt
drwxr-xr-x. 2 root root    79 Dec 25 09:56 build_images
drwxr-xr-x. 2 root root     6 Jan 15 16:05 container
drwxr-xr-x. 4 root root    30 Jan 10 15:47 dcos
drwxr-xr-x. 3 root root    28 Feb  8 11:06 docker
drwxr-xr-x. 3 root root  4096 Feb 22 15:31 python_learning
drwxr-xr-x. 5 root root    36 Dec 11 11:07 zookeeper
0
>>> subprocess.call('ls -l /data/ ; ls notexistfile',shell=True)
total 20
-rw-r--r--. 1 root root 10240 Feb  8 11:06 1.tar
-rw-r--r--. 1 root root    24 Feb 22 14:56 1.txt
drwxr-xr-x. 2 root root    79 Dec 25 09:56 build_images
drwxr-xr-x. 2 root root     6 Jan 15 16:05 container
drwxr-xr-x. 4 root root    30 Jan 10 15:47 dcos
drwxr-xr-x. 3 root root    28 Feb  8 11:06 docker
drwxr-xr-x. 3 root root  4096 Feb 22 15:31 python_learning
drwxr-xr-x. 5 root root    36 Dec 11 11:07 zookeeper
ls: cannot access notexistfile: No such file or directory
2

Tips:

使用shlex.split()方法序列化args

>>> import shlex,subprocess
>>> cmd=input()
/bin/echo -e -n "this a test '$CASE'"
>>> args=shlex.split(cmd)
>>> print (args)
['/bin/echo', '-e', '-n', "this a test '$CASE'"]
>>> p = subprocess.Popen(args)
>>> this a test '$CASE'
  • subprocess.check_call()

subprocess.check_call(args, *,stdin=None, stdout=None, stderr=None, shell=False, cwd=None, timeout=None)

Run command with arguments. Wait for command to complete. If the return code was zero then return, otherwise raise CalledProcessError. The CalledProcessError object will have the return code in the returncode attribute.

运行命令,父进程等待子进程完成(阻塞),如果子进程返回的returncode不为0的话,将抛出CalledProcessError异常。在异常对象中,包括进程的returncode信息。

>>> subprocess.check_call('ls -l /data/;ls notexistfile',shell=True)
total 20
-rw-r--r--. 1 root root 10240 Feb  8 11:06 1.tar
-rw-r--r--. 1 root root    24 Feb 22 14:56 1.txt
drwxr-xr-x. 2 root root    79 Dec 25 09:56 build_images
drwxr-xr-x. 2 root root     6 Jan 15 16:05 container
drwxr-xr-x. 4 root root    30 Jan 10 15:47 dcos
drwxr-xr-x. 3 root root    28 Feb  8 11:06 docker
drwxr-xr-x. 3 root root  4096 Feb 22 15:31 python_learning
drwxr-xr-x. 5 root root    36 Dec 11 11:07 zookeeper
ls: cannot access notexistfile: No such file or directory
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib64/python3.6/subprocess.py", line 291, in check_call
    raise CalledProcessError(retcode, cmd)
subprocess.CalledProcessError: Command 'ls -l /data/;ls notexistfile' returned non-zero exit status 2.
  • subprocess.check_output()

subprocess.check_output(args, *, stdin=None, stderr=None, shell=False, cwd=None, encoding=None, errors=None, universal_newlines=False, timeout=None)

Run command with arguments and return its output.

If the return code was non-zero it raises a CalledProcessError. The CalledProcessError object will have the return code in the returncode attribute and any output in the output attribute.

运行命令,父进程等待子进程完成(阻塞),并返回子进程的标准输出,如果子进程返回的returncode不为0的话,将抛出CalledProcessError异常。在异常对象中,包括进程的returncode信息和标准输出的信息

>>> subprocess.check_output('ls -l',shell=True)        
b'total 32\n-rw-r--r--. 1 root root  164 Feb 22 19:28 \\\n-rwxr-xr-x. 1 root root  229 Feb 22 10:24 getIP.py\n-rwxr-xr-x. 1 root root  253 Feb  8 13:37 sh-commands.py\n-rwxr-xr-x. 1 root root  369 Feb 22 15:31 sh-popen.py\n-rwxr-xr-x. 1 root root  572 Feb  8 20:44 sh-subprocess.py\n-rwxr-xr-x. 1 root root  407 Feb 22 14:46 sh-system.py\n-rwxr-xr-x. 1 root root   72 Feb  8 13:24 test.sh\ndrwxr-xr-x. 4 root root   42 Feb  8 18:29 zipdir\n-rwxr-xr-x. 1 root root 1046 Feb  8 19:00 zip.py\n'
>>> subprocess.check_output('ls notexistfile',shell=True) 
ls: cannot access notexistfile: No such file or directory
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib64/python3.6/subprocess.py", line 336, in check_output
    **kwargs).stdout
  File "/usr/lib64/python3.6/subprocess.py", line 418, in run
    output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command 'ls notexistfile' returned non-zero exit status 2.

使用try和except语句处理subprocess.CalledProcessError异常

#!/usr/bin/env python36
import subprocess
code_flag=0
try:
        p2=subprocess.check_output('ls notexistfile',shell=True)
        print(p2)
except subprocess.CalledProcessError:
        #print ('returncode is not 0')
        code_flag=2

if code_flag != 0:
        print ('execute cmd failed')

如果要获取标准错误,需定义stderr参数 stderr=subprocess.STDOUT

>>> subprocess.check_output(
...     "ls non_existent_file; exit 0",
...     stderr=subprocess.STDOUT,
...     shell=True)
'ls: non_existent_file: No such file or directory\n'
  • subprocess.Popen()

经常的使用方法为subporcess.Popen, 我们可以在Popen()建立子进程的时候改变标准输入、标准输出和标准错误,并可以利用subprocess.PIPE将多个子进程的输入和输出连接在一起,构成管道(pipe):

import subprocess
child1 = subprocess.Popen(["ls","-l"], stdout=subprocess.PIPE)
child2 = subprocess.Popen(["wc"], stdin=child1.stdout,stdout=subprocess.PIPE)
out = child2.communicate()
print(out)

subprocess.PIPE

在创建Popen对象时,subprocess.PIPE可以初始化stdin, stdout或stderr参数。表示与子进程通信的标准流,它实际上为文本流提供一个缓存区,缓存区有一定的容量限制,当缓存区满了之后,子进程就会停止写入数据,程序就会卡住

Popen对象创建后,主程序不会自动等待子进程完成。我们必须调用对象的wait()方法,父进程才会等待 (也就是阻塞block)。这里要注意的是如果进程输出文本数据超过buffersize(默认64k),调用wait()方法会使程序锁死,推荐使用communicate()方法(communicate()是Popen对象的一个方法,该方法也会阻塞父进程,直到子进程完成 ),这个方法会把输出放在内存,而不是管道里,所以这时候上限就和内存大小有关了,一般不会有问题。而且如果要获得程序返回值,可以在调用 Popen.communicate() 之后取 Popen.returncode 的值。但如果超过内存,那么要考虑比如文件 stdout=open(“process.out”, “w”) 的方式来解决,不能再使用管道了

Popen对象的方法:

Popen.poll() 用于检查子进程是否已经结束。设置并返回returncode属性。

Popen.wait() 等待子进程结束。设置并返回returncode属性。

Popen.communicate(input=None) 与子进程进行交互。向stdin发送数据,或从stdout和stderr中读取数据。可选参数input指定发送到子进程的参数。 Communicate()返回一个元组:(stdoutdata, stderrdata)。注意:如果希望通过进程的stdin向其发送数据,在创建Popen对象的时候,参数stdin必须被设置为PIPE。同样,如 果希望从stdout和stderr获取数据,必须将stdout和stderr设置为PIPE。

Popen.send_signal(signal) 向子进程发送信号。

Popen.terminate() 停止(stop)子进程。在windows平台下,该方法将调用Windows API TerminateProcess()来结束子进程。

Popen.kill() 杀死子进程。

Popen.stdin 如果在创建Popen对象是,参数stdin被设置为PIPE,Popen.stdin将返回一个文件对象用于策子进程发送指令。否则返回None。

Popen.stdout 如果在创建Popen对象是,参数stdout被设置为PIPE,Popen.stdout将返回一个文件对象用于策子进程发送指令。否则返回 None。(类似于os.popen()方法

Popen.stderr 如果在创建Popen对象是,参数stdout被设置为PIPE,Popen.stdout将返回一个文件对象用于策子进程发送指令。否则返回 None。

Popen.pid 获取子进程的进程ID。

Popen.returncode 获取进程的返回值。如果进程还没有结束,返回None。

实例一 :

使用Popen的communicate()方法保存子进程输出信息,避免程序死锁

[root@master python_learning]# vim sh-subprocess.py  
#!/usr/bin/env python36
#-*- coding:utf-8 -*-
import os,sys
import subprocess

if len(sys.argv) < 3 :
        print ('2 arguments is needed')
        sys.exit(1)

cmd = './test.sh %s %s'%(sys.argv[1],sys.argv[2])

p=subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE)
#p.wait()                                         #阻塞父进程,等待cmd执行完成
#print(p.stdout.read())                           #从管道读出输出信息
output,errors=p.communicate()                     #输出信息缓存到内存中;进程结束后,返回元组(stdoutdata, stderrdata)
print(output)                                     #打印stdoutdata
if p.returncode != 0:
        print ("execute shell script failed")

实例二 :

env参数默认为None,子进程默认继承父进程的环境变量。但是,一旦你自定义了env的值(类型为字典),则子程序的环境变量全部由env参数决定,与父进程无关

>>> p=subprocess.Popen('echo this is a test "$case"',env={'case':'123456'},shell=True,stdout=subprocess.PIPE)
>>> print(p.stdout.read())
b'this is 123456\n'
方法五
#

5.sh模块

参考 官方文档

  • 调用常用命令
>>> import sh
>>> sh.ls()
getIP.py        sh-popen.py       sh-system.py  test.sh  zip.py
sh-commands.py  sh-subprocess.py  sub1.py       zipdir
>>> sh.ls('-l','./getIP.py')                              #命令参数以字符串形式传递
-rwxr-xr-x. 1 root root 229 Feb 22 10:24 ./getIP.py
或者
>>> from sh import ls
>>> ls()
getIP.py        sh-popen.py       sh-system.py  test.sh  zip.py
sh-commands.py  sh-subprocess.py  sub1.py       zipdir
>>> ls('-l','./getIP.py')   
-rwxr-xr-x. 1 root root 229 Feb 22 10:24 ./getIP.py

未完待续。。。

Related

批量自动构建docker镜像的shell脚本
·580 字·3 分钟
Docker Shell