一、Python執行外部指令
1、subprocess子產品簡介
subprocess 子產品允許我們啟動一個新程序,并連接配接到它們的輸入/輸出/錯誤管道,進而擷取傳回值。
這個子產品用來建立和管理子程序。它提供了高層次的接口,用來替換os.system*()、 os.spawn*()、 os.popen*()、os,popen2.*()和commands.*等子產品和函數。
subprocess提供了一個名為Popen的類啟動和設定子程序的參數,由于這個類比較複雜, subprocess還提供了若幹便利的函數,這些函數都是對Popen類的封裝。
2、subprocess子產品的周遊函數
linux安裝ipython
pip3 install ipython
(1)call函數
call函數的定義如下:
subprocess.ca11(args, *, stdin=None, stdout=None, stderr=None, she11=False)
#運作由args參數提供的指令,等待指令執行結束并傳回傳回碼。args參數由字元串形式提供且有多個指令參數時,需要提供shell=True參數
- args:表示要執行的指令。必須是一個字元串,字元串參數清單。
- stdin、stdout 和 stderr:子程序的标準輸入、輸出和錯誤。其值可以是 subprocess.PIPE、subprocess.DEVNULL、一個已經存在的檔案描述符、已經打開的檔案對象或者 None。subprocess.PIPE 表示為子程序建立新的管道。subprocess.DEVNULL 表示使用 os.devnull。預設使用的是 None,表示什麼都不做。另外,stderr 可以合并到 stdout 裡一起輸出。
- shell:如果該參數為 True,将通過作業系統的 shell 執行指定的指令。
示例代碼:
[root@python ~]# ipython #啟動ipython
Python 3.8.1 (default, Mar 9 2020, 12:35:12)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.13.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]: import subprocess #調用函數
In [2]: subprocess.call(['ls','-l'])
drwxr-xr-x. 2 root root 6 10月 31 23:04 公共
drwxr-xr-x. 2 root root 6 10月 31 23:04 模闆
drwxr-xr-x. 2 root root 6 10月 31 23:04 視訊
drwxr-xr-x. 2 root root 4096 10月 31 22:40 圖檔
drwxr-xr-x. 2 root root 6 10月 31 23:04 文檔
drwxr-xr-x. 2 root root 6 10月 31 23:04 下載下傳
drwxr-xr-x. 2 root root 6 10月 31 23:04 音樂
drwxr-xr-x. 2 root root 6 10月 31 15:27 桌面
Out[2]: 0
In [3]: subprocess.call('exit 1',shell=True)
Out[3]: 1
(2)check_call函數
check_call函數的作用與call函數類似,差別在于異常情況下傳回的形式不同。
對于call函數,工程師通過捕獲call指令的傳回值判斷指令是否執行成功,如果成功則傳回0,否則的話傳回非0,對于check_call函數,如果執行成功,傳回0,如果執行失敗,抛出subrocess.CalledProcessError異常。如下所示:
In [5]: subprocess.check_call(['ls','-l'])
drwxr-xr-x. 2 root root 6 10月 31 23:04 公共
drwxr-xr-x. 2 root root 6 10月 31 23:04 模闆
drwxr-xr-x. 2 root root 6 10月 31 23:04 視訊
drwxr-xr-x. 2 root root 4096 10月 31 22:40 圖檔
drwxr-xr-x. 2 root root 6 10月 31 23:04 文檔
drwxr-xr-x. 2 root root 6 10月 31 23:04 下載下傳
drwxr-xr-x. 2 root root 6 10月 31 23:04 音樂
drwxr-xr-x. 2 root root 6 10月 31 15:27 桌面
Out[5]: 0
In [6]: subprocess.check_call('exit 1',shell=True)
-------------------------------------------------------------
CalledProcessError Traceback (most recent call last)
<ipython-input-6-5e148d3ce640> in <module>
----> 1 subprocess.check_call('exit 1',shell=True)
/usr/local/python381/lib/python3.8/subprocess.py in check_call(*popenargs, **kwargs)
362 if cmd is None:
363 cmd = popenargs[0]
--> 364 raise CalledProcessError(retcode, cmd)
365 return 0
366
CalledProcessError: Command 'exit 1' returned non-zero exit status 1.
(3)check_output
Python3中的subprocess.check_output函數可以執行一條sh指令,并傳回指令的輸出内容,用法如下:
In [10]: output = subprocess.check_output(['df','-h'])
In [11]: print(output.decode())
檔案系統 容量 已用 可用 已用% 挂載點
/dev/mapper/cl-root 17G 5.2G 12G 31% /
devtmpfs 473M 0 473M 0% /dev
tmpfs 489M 92K 489M 1% /dev/shm
tmpfs 489M 7.1M 482M 2% /run
tmpfs 489M 0 489M 0% /sys/fs/cgroup
/dev/sda1 1014M 173M 842M 18% /boot
tmpfs 98M 16K 98M 1% /run/user/42
tmpfs 98M 0 98M 0% /run/user/0
In [12]: lines = output.decode().split('\n')
In [13]: lines
Out[13]:
['檔案系統 容量 已用 可用 已用% 挂載點',
'/dev/mapper/cl-root 17G 5.2G 12G 31% /',
'devtmpfs 473M 0 473M 0% /dev',
'tmpfs 489M 92K 489M 1% /dev/shm',
'tmpfs 489M 7.1M 482M 2% /run',
'tmpfs 489M 0 489M 0% /sys/fs/cgroup',
'/dev/sda1 1014M 173M 842M 18% /boot',
'tmpfs 98M 16K 98M 1% /run/user/42',
'tmpfs 98M 0 98M 0% /run/user/0',
'']
In [14]: for line in lines[1:-1]:
...: if line:
...: print(line.split()[-2])
...: #截取挂載點資料
31%
0%
1%
2%
0%
18%
1%
0%
在子程序執行指令,以字元串形式傳回執行結果的輸出。如果子程序退出碼不是0,抛出subprocess.CalledProcessError異常,異常的output字段包含錯誤輸出:
In [19]: try:
...: output = subprocess.check_output(['df','-h']).decode() #正确的
...: except subprocess.CalledProcessError as e:
...: output = e.output
...: code = e.returncode
//正确的沒有任何輸出
In [23]: try:
...: output = subprocess.check_output(['wsd','-h'], stderr=subprocess.STDOUT)
...: .decode() #錯誤的
...: except subprocess.CalledProcessError as e:
...: output = e.output
...: code = e.returncode
...:
//前面的錯誤代碼省略
FileNotFoundError: [Errno 2] No such file or directory: 'wsd'
3、subprocess子產品的Popen類(PyCharm)
實際上,我們上面的三個函數都是基于Popen()的封裝(wrapper)。這些封裝的目的在于讓我們容易使用子程序。當我們想要更個性化我們的需求的時候,就要轉向Popen類,該類生成的對象用來代表子程序。
-
子產品中基本的程序建立和管理由subprocess
類來處理Popen
-
是用來替代subprocess.popen
的os.popen
- Popen 是
的核心,子程序的建立和管理都靠它處理。subprocess
構造函數:
class 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=False,
startupinfo=None, creationflags=0,restore_signals=True, start_new_session=False, pass_fds=(),
*, encoding=None, errors=None)
(1)常用參數:
- args:shell指令,可以是字元串或者序列類型(如:list,元組)
-
bufsize:緩沖區大小。當建立标準流的管道對象時使用,預設-1。
0:不使用緩沖區
1:表示行緩沖,僅當universal_newlines=True時可用,也就是文本模式
正數:表示緩沖區大小
負數:表示使用系統預設的緩沖區大小。
- stdin, stdout, stderr:分别表示程式的标準輸入、輸出、錯誤句柄
- preexec_fn:隻在 Unix 平台下有效,用于指定一個可執行對象(callable object),它将在子程序運作之前被調用
- cwd:用于設定子程序的目前目錄。
- env:用于指定子程序的環境變量。如果 env = None,子程序的環境變量将從父程序中繼承。
建立一個子程序,然後執行一個簡單的指令:
>>> import subprocess
>>> p = subprocess.Popen('ls -l', shell=True)
>>> total 164
-rw-r--r-- 1 root root 133 Jul 4 16:25 admin-openrc.sh
-rw-r--r-- 1 root root 268 Jul 10 15:55 admin-openrc-v3.sh
...
>>> p.returncode
>>> p.wait()
0
>>> p.returncode
0
這裡也可以使用
p = subprocess.Popen(['ls', '-cl'])
來建立子程序。
(2)Popen 對象的屬性
<1> p.pid
:
p.pid
子程序的PID。
<2> p.returncode
p.returncode
該屬性表示子程序的傳回狀态,returncode可能有多重情況:
- None —— 子程序尚未結束;
- ==0 —— 子程序正常退出;
- \> 0—— 子程序異常退出,returncode對應于出錯碼;
- < 0—— 子程序被信号殺掉了。
<3> p.stdin, p.stdout, p.stderr
p.stdin, p.stdout, p.stderr
子程序對應的一些初始檔案,如果調用Popen()的時候對應的參數是subprocess.PIPE,則這裡對應的屬性是一個包裹了這個管道的 file 對象。
(3)Popen 對象方法
- poll(): 檢查程序是否終止,如果終止傳回 returncode,否則傳回 None。
- wait(timeout): 等待子程序終止。
- communicate(input,timeout): 和子程序互動,發送和讀取資料。
- send_signal(singnal): 發送信号到子程序 。
- terminate(): 停止子程序,也就是發送SIGTERM信号到子程序。
- kill(): 殺死子程序。發送 SIGKILL 信号到子程序。
子程序的PID存儲在child.pid
import time
import subprocess
def cmd(command):
subp = subprocess.Popen(command,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,encoding="utf-8")
subp.wait(2)
if subp.poll() == 0:
print(subp.communicate()[1])
else:
print("失敗")
cmd("java -version")
cmd("exit 1")
輸出結果如下:
java version "1.8.0_31" Java(TM) SE Runtime Environment (build 1.8.0_31-b13) Java HotSpot(TM) 64-Bit Server VM (build 25.31-b07, mixed mode) 失敗
(4)子程序的文本流控制
(沿用child子程序) 子程序的标準輸入,标準輸出和标準錯誤也可以通過如下屬性表示:
- child.stdin
- child.stdout
- child.stderr
我們可以在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)
執行結果如下:
(b' 2 11 60\n', None)
subprocess.PIPE實際上為文本流提供一個緩存區。child1的stdout将文本輸出到緩存區,随後child2的stdin從該PIPE中将文本讀取走。child2的輸出文本也被存放在PIPE中,直到communicate()方法從PIPE中讀取出PIPE中的文本。
要注意的是,communicate()是Popen對象的一個方法,該方法會阻塞父程序,直到子程序完成。
我們還可以利用communicate()方法來使用PIPE給子程序輸入:
import subprocess
child = subprocess.Popen(["cat"], stdin=subprocess.PIPE)
child.communicate("vamei".encode())
我們啟動子程序之後,cat會等待輸入,直到我們用communicate()輸入"vamei"。
通過使用subprocess包,我們可以運作外部程式。這極大的拓展了Python的功能。如果你已經了解了作業系統的某些應用,你可以從Python中直接調用該應用(而不是完全依賴Python),并将應用的結果輸出給Python,并讓Python繼續處理。shell的功能(比如利用文本流連接配接各個應用),就可以在Python中實作。
4、使用python自動安i裝并啟動mongodb
PyCharm記得連接配接linux
簡易流程
- Python自動化運維 --> 基于shell指令進行封裝
- 編寫自動化腳本 --> 用Python文法封裝shell指令的執行過程
- python執行shell指令 --> python外部指令
- python函數執行shell指令
- os.system(cmd):執行cmd指令
- subprocess子產品
-
subprocess.call(['ls','-l']) subprocess.call('ll' , shell=True) 運作成功: 傳回0 運作失敗: 傳回非0
-
subprocess. check_call (['ls', '-l']) subprocess. check_call ('ll', shell=True) 運作成功: 傳回0 運作失敗: 傳回CalledProcessError
-
subprocess. check_ output(['cat', 'apache.log'], stderr= subprocess.STDOUT) 運作成功:傳回指令的輸出結果 運作失敗:自定義錯誤輸出stderr
- subprocess子產品的Popen類
(1)PyCharm建立檔案
# coding=utf-8
import subprocess
import os
import shutil
import tarfile
# 執行外部指令的函數
def execute_cmd(cmd):
'''執行shell指令'''
p = subprocess.Popen(cmd, shell=True,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout, stderr = p.communicate()
if p.returncode != 0:
return p.returncode, stderr
return p.returncode, stdout
# 解壓
def unpackage_mongo(package, package_dir):
# 擷取MongoDB壓縮包的主檔案名,也就是解壓後的目錄名稱
# mongodb-linux-x86_64-rhe170-4.2.3
unpackage_dir = os.path.splitext(package)[0]
if os.path.exists(unpackage_dir):
shutil.rmtree(unpackage_dir)
if os.path.exists(package_dir):
shutil.rmtree(package_dir)
# 解壓
try:
t = tarfile.open(package, 'r:gz')
t.extractall('.')
print('tar is ok.')
except Exception as e:
print(e)
# 重命名
shutil.move(unpackage_dir, 'mongo')
# 建立mongodata
def create_datadir(data_dir):
if os.path.exists(data_dir):
shutil.rmtree(data_dir)
os.mkdir(data_dir)
# 拼接啟動MongoDB
def format_mongod_commamd(package_dir, data_dir, logfile):
# mongo/bin/mongod
mongod = os.path.join(package_dir, 'bin', 'mongod')
# mongo/bin/mongod --fork --logpath mongodata/mongod.log --dbpath mongodata
mongod_format = """{0} --fork --dbpath {1} --logpath {2}"""
return mongod_format.format(mongod, data_dir, logfile)
# 啟動MongoDB
def start_mongod(cmd):
returncode, out = execute_cmd(cmd)
if returncode != 0:
raise SystemExit('execute {0} error:{1}'.format(cmd, out))
else:
print('execute {0} successfuly.'.format(cmd))
#入口函數
def main():
package = 'mongodb-linux-x86_64-rhel70-4.2.3.tgz'
cur_dir = os.path.abspath('.')
package_dir = os.path.join(cur_dir, 'mongo')
data_dir = os.path.join(cur_dir, 'mongodata')
logfile = os.path.join(data_dir, 'mongod.log')
# 判斷MongoDB壓縮包是否存在
if not os.path.exists(package):
raise SystemExit('{0} not found.'.format(package))
# 解壓
unpackage_mongo(package, package_dir)
create_datadir(data_dir)
# 啟動mongodb
start_mongod(format_mongod_commamd(package_dir, data_dir, logfile))
# 配置環境變量
os.system('echo "export PATH=./mongo/bin:$PATH" > ~/.bash_profile')
os.system('source ~/.bash_profile')
os.system('./mongo/bin/mongo')
main()
- 在這段程式中,我們首先在main函數中定義了幾個變量,包括目前目錄的路徑、MongoDB二進制檔案所在的路徑、MongoDB資料目錄所在的路徑,以及MongoDB的日志檔案。
- 随後,我們判斷MongoDB的安裝包是否存在,如果不存在,則通過抛出SystemExit異常的方式結束程式。
- 在unpackage_mongo函數中,我們通過Python程式得到MongoDB安裝包解壓以後的目錄。如果目錄已經存在,則删除該目錄。随後,我們使用tarfile解MongoDB資料庫,解壓完成後,将指令重命名為mongo目錄。
- 在create_datadir目錄中,我們首先判斷MongoDB資料庫目錄是否存在,如果存在,則删除該目錄,随後再建立MongoDB資料庫目錄。
- 在start_mongod函數中, 我們執行MongoDB資料庫的啟動指令啟動MongoDB資料庫。為了在Python代碼中執行shell指令,我們使用了subprocess庫。 我們将subprocess庫執行she11指令的邏輯封裝成execute_cmd函數,在執行shell指令時,直接調用該函數即可。
(2)将PyCharm中的檔案上傳到Linux
如果,是直接調用Linux中檔案可用:
如果是本地建立:
(3)Linux執行腳本,并測試
[root@python opt]# python auto_install_mongodb.py #執行提前編寫好的腳本
tar is ok.
execute /opt/mongo/bin/mongod --fork --dbpath /opt/mongodata --logpath /opt/mongodata/mongod.log successfuly.
[root@python opt]# netstat -anpt | grep mongo #檢視mongo是否啟動
tcp 0 0 127.0.0.1:27017 0.0.0.0:* LISTEN 4616mongod
[root@python opt]# ls #檢視是否生成mongo目錄
01find_cmd.py bb.bmp mongodb-linux-x86_64-rhel70-4.2.3.tgz
aaa.jpg cc.png rh
adc.txt mongo subprocess_demo
auto_install_mongodb.py mongodata
[root@python opt]# cd mongo
[root@python mongo]# cd bin/
[root@python bin]# ./mongo #進入mongo
MongoDB shell version v4.2.3
connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("c302ff50-7e27-40b7-8046-8441af8cb965") }
MongoDB server version: 4.2.3
> show databases; #檢視資料庫
admin 0.000GB
config 0.000GB
local 0.000GB