使用 Python 編寫腳本並發布
P1: 腳本
通常在 Linux 服務器上會遇到在命令行中輸入命令的操作,而有些操作包含的命令數目較多或者其中的命令包含的參數較多,如果一個一個的敲命令的話就太麻煩了,有幾種做法可以簡化操作:
- 使用 alias 為命令編寫別名,比如我之前開發一個網站程序 minor-sspymgr 時,經常需要上傳修改后的代碼,更新服務器上的代碼,重啟網站程序。為了方便,我定義一個 alias 別名命令:
alias updateMgr='cd ~/minor-sspymgr/ && git pull origin master && pm2 restart sspymgr'
這種方法的優點是很方便簡單,把常用的命令組合到一起,用一個別名表示即可使用。當然,它的缺點也很明顯,就是不能執行一些復雜的命令,而且難以將參數應用到命令中。
- 編寫一個 .sh 腳本,我之前寫簡單腳本時用的都是 bash,對於上述例子,用 sh 腳本編寫起來也很類似,可以簡單的把上述命令以
&&
拆分為 3 行:
#!/bin/bash
cd ~/minor-sspymgr/
git pull origin master
pm2 restart sspymgr
當然了,實際的腳本遠非如此簡單,可以用 $1
... $n
獲取命令行參數,執行一些更復雜的邏輯。
為什么用 Python 寫腳本
既然用 bash 已經可以編寫一些腳本了,那么為什么我還要用 Python 編寫腳本呢?原因有兩個:1. bash 用的不多,學的也早,而且 bash 和常用的編程語言的語法有些差別,很多內容都容易忘記,用 Python 寫的話也可以熟悉 Python(盡管我是從 flask 制作一個 WSGIServer 開始使用 Python 的),2. Python 的庫很多也很方便使用,編寫腳本給人一種流暢的感覺。當然最主要的原因就是因為 Python 里面有 argparse 這個庫,對於解析命令行參數來說十分方便。
如何解析命令行參數
bash 腳本里面獲取命令行參數的方式很簡單,但是解析起來卻比較麻煩,如果有 bash 解析命令行參數的庫,希望可以推薦給我。不過即使有這樣的庫,估計我還是會選擇 Python 來編寫腳本了。在 Python 里解析命令行參數的模塊比較多,有 getopt
、optparse
、argparse
等 1。目前我只用過 argparse
,因此這里也只會涉及到如何用 argparse 來解析命令行參數。至於前兩個模塊,如果有興趣的話,可以自行了解。argparse 使用起來非常簡單:
from argparse import ArgumentParser
parser = ArgumentParser(description="""Run this script under SUPER USER privilege. Create or remove git repository
under direcotry: {}. Currently only works for Linux. Most operations are only tested under Linux.
Script version : {}.
""".format(config["repo_dir"], __version__))
parser.add_argument("-V", "--version", help="Print this script version", action="store_true")
args = parser.parse_args()
if args.version:
print("Script version is {}".format(__version__))
上面這幾行代碼就做好了一個簡單的命令行解析功能,當我們輸入 script.py -V
的時候就會打印出腳本的版本。不需要在意命令行參數的位置或者是否必須要加上這個參數,只需要將注意力放在代碼的邏輯上即可,argparse 這個模塊可以極大的方便我們解析命令行參數。
P2: 示例:初始化 git 倉庫的腳本
之前用 git 搭建過源代碼管理服務器 2、3。如果要在這樣的服務器上添加一個共享倉庫,就需要在倉庫根目錄下面執行一些操作了。比如說,我的 git 倉庫存放的目錄是 /src
這個目錄下面的所有文件及文件夾的屬主和屬組都是 git,要想新建一個共享倉庫,最開始我的做法是:
- cd /src
- mkdir newrepo && cd newrepo
- git init --bare --shared
- cd ..
- sudo chown -R git:git newrepo && sudo chmod -R g+rwx newrepo
步驟還是有點多的,每次都要敲這么多命令很麻煩,把這些命令寫到 bash 腳本中,比如叫做 gitrepo.sh
,給它加上 x 執行權限,在終端里輸入 sudo gitrepo.sh reponame
就能完成上面的操作。
用 Bash 編寫的腳本
Bash 腳本,編寫簡單,就是把在命令行中敲過的命令依次寫到文件中即可,但是對於不常寫 bash 腳本的我來說,要編寫一個完備的腳本,並且要包含參數、異常處理等邏輯來說比較麻煩:
#!/bin/bash
# filename: gitrepo.sh
# @author BriFuture
# @details create bare and shared repository within /src directory
repoName=$1
if [ -z "$repoName" ]; then
echo "Repo is empty!"
exit 1
fi
dotpos=`expr index "$repoName" "."`
if [ "$dotpos" -gt 0 ]; then
echo "found";
else
repoName=$repoName".git"
fi
# go to the repository dir
cd /src
mkdir $repoName
cd $repoName
echo "Init git repo in $repoName"
git init --bare --shared
# change the own of repository
cd ../
sudo chown -R git:git $repoName
sudo chmod -R g+rwx $repoName
用 Python 編寫的腳本
實際上,我用 Python 重新編寫這個腳本時,在參數中加入了更多的可選項,此前的 bash 腳本只能夠添加倉庫,這個全新的腳本加入了其他操作:添加新的倉庫,列出所有倉庫,刪除一個倉庫。並且完善了命令行的幫助信息,為腳本添加了配置文件以及輸出日志。
另外在執行腳本的過程中需要用到超級用戶的權限,因此在腳本中添加了檢查當前用戶權限的功能:
def is_root():
if hasattr(os, "getuid"):
return os.getuid() == 0
return False
該 Python 腳本的主要功能是添加和刪除倉庫(目錄),添加倉庫的操作如下,分別是創建目錄,執行 git init --bare --shared
命令,更改文件屬主和屬組的操作(實際的腳本中包含一些日志記錄和異常處理的代碼):
from pathlib import Path
def createRepo(repo: Path):
if repo.exists():
return
repo.mkdir(parents=True)
subprocess.run(["git", "init", "--bare", "--shared", str(repo.absolute())])
shutil.chown(repo, config["user"], config["group"])
刪除倉庫的操作更加簡單,調用 shutil 遞歸刪除文件夾即可,不能使用 repo.rmdir() 函數,因為一般倉庫文件夾是非空的:
import shutil
def deleteRepo(repo: Path):
if not repo.exists():
return
shutil.rmtree(str(repo))
羅列所有倉庫使用 Path.iterdir()
函數即可。
這樣一個可以使用的腳本就制作完成了,如果只是單純的編寫一個腳本的話,可以在文件的開頭加上 #!/usr/bin/python3
這樣的標記,表明這是一個可執行的 Python3 腳本,在 linux 系統上給它加上 x 權限,比如這個文件的名稱為 gitrepo.py
,我們可以在終端中輸入 gitrepo.py -h
或者 python3 gitrepo.py -h
查看這個命令的幫助信息。
詳細的代碼可以在 Github 上的 gitrepo.py 文件中查看。
P3: 以 wheel 包的形式發布
前面說過,我們可以把寫好的 Python 文件當做腳本執行,但是每次都要敲 gitrepo.py -h
這樣的命令,能不能就像平時用 ls
這些命令一樣直接敲 gitrepo -h
呢?最簡單的是用 ln 建立軟鏈接:sudo ln -s /path/to/gitrepo.py /usr/bin/gitrepo
,但是這里我們可以借助 pypi 發布我們已經寫好的命令,將我們的腳本發布到 pypi 上,那么還可以在沒有該腳本的機器上利用 pip 進行安裝。
以 brifuture-facilities 這個項目為例,在項目的根目錄下面新建一個 setup.py
文件,輸入以下內容:
# -*- coding: utf-8 -*-
from setuptools import setup, find_packages
with open("README.md", "r", encoding="utf-8") as fh:
long_description = fh.read()
with open('requirements.txt', "r", encoding="utf-8") as f:
requires = f.read().splitlines()
from myfacilities import __version__
setup(
name = "brifuture-facilities",
packages = find_packages(where='.'),
version = __version__,
entry_points = {
"console_scripts": [
'bf_broadcast = myfacilities.broadcast:main',
'bf_gitrepo = myfacilities.gitrepo:main',
]
},
description = "BriFuture's scripts set, all scripts will be written with Python3",
author = "BriFuture",
author_email = "jw.brifuture@gmail.com",
license = "GPLv3",
url = "http://github.com/brifuture/",
install_requires = requires,
include_package_data = True,
zip_safe=True,
exclude_package_data = {'': ['__pycache__']},
# download_url = "",
keywords = [ "webserver", "socks-manager" ],
classifiers = [
"Programming Language :: Python",
"Programming Language :: Python :: 3" ,
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Operating System :: OS Independent"
],
long_description = long_description,
long_description_content_type="text/markdown",
)
我們需要利用的是 setuptools,從這個模塊中導入 setup,它的參數都很直觀,我們想要添加一個可執行的腳本時,在 entry_points 關鍵字參數中添加即可,如:
entry_points = {
"console_scripts": [
'bf_broadcast = myfacilities.broadcast:main',
'bf_gitrepo = myfacilities.gitrepo:main',
]
},
由於這個項目中的文件是包的一部分,所以我沒法直接使用 python 運行其中的某個腳本,像 python3 myfacilities.gitrepo -h
是無法運行的,但是借助於 setuptools,我們可以將寫好的程序安裝到機器中,python3 setup.py install
可以通過 setup.py
文件進行安裝,也可以使用 pip install .
進行安裝,詳細的教程可以在 文檔 中找到。
接下來制作 wheel 包,命令很簡單:
python3 setup.py sdist bdist_wheel
這樣會將我們的代碼打包到 dist/
目錄下。
我們需要將制作好的程序發布到 pypi 上,安裝好 twine: pip install twine
, 在 $HOME
目錄(linux) 或者 C:\Users\yourname\
目錄(windows)下新建一個 .pypirc
文件,輸入下面的內容:
[distutils]
index-servers=
pypi
testpypi
[pypi]
repository: https://upload.pypi.org/legacy/
username: yourname
password: yourpasswd
[testpypi]
repository: https://test.pypi.org/legacy/
username: yourname
password: yourpasswd
接下來就可以發布了,但是不要着急,正式發布前都請將制作好的包發布到 testpypi 上:
python3 -m twine upload -r testpypi dist/* --skip-existing
等你確認一切無誤后,在將 -r testpypi
參數換成 -r pypi
以便正式發布。
P4: 小結
-
盡管制作一個小巧、實用而且完備的 Python 腳本很有意思,但是要想做出一個實用、對用戶友好的腳本,還是需要花一些時間的。實際上如果要為用戶提供更加友好的實用方式,可以考慮以界面的形式為用戶提供操作,比如說以網頁的形式提供操作,在服務器后台對用戶操作進行處理。不過你也可能注意到了,這個腳本的功能其實就是很基礎的增刪查改。
-
上面示例的 Python 腳本在使用過程中會用到超級用戶權限,為了防止操作失敗,在添加、刪除倉庫時必須提供超級用戶權限,否則腳本將會退出,還有一種方式可以在運行時獲得超級用戶權限,就是使用 [elevate][https://github.com/barneygale/elevate] 進行提權,具體的使用方式還沒來得及看,不過我猜測應該需要向 elevate 提供一個超級用戶密碼的文件。
如果你想仔細查看上述 Python 示例代碼的話,可以在 Github 上找到 brifuture-facilities 項目的代碼。如果你覺得我的文章或者其中的代碼對你有幫助,請給它點個贊。
參考
掘金:Python中最好用的命令行參數解析工具
cnblogs:搭建簡單的Git服務器
cnblogs:lighttpd 與 gitweb 搭建服務器
setuptools documentation