這篇文章介紹如何寫個系統命令以及我爲什麼要寫命令
“一切皆文件”是linux的基本哲學之一,我們在linux下執行的諸如ls
之類的命令實際上都是去執行了系統上的某個文件,which
命令可以查看到我們執行的命令對應的是系統上的哪個文件,例如常用的ls
命令實際上就是執行了/bin/ls
這個文件
root@ops-coffee:~# which ls
/bin/ls
基於此,我們就知道了定義一個命令很簡單,只需要寫個可執行的文件就行了,python的標準模塊argparse
就可以幫助我們快速方便的構建一個用戶友好的命令
argparse
相比於自己實現個命令文件,argparse
模塊能夠自動生成幫助和使用手冊,並在用戶傳入無效參數時報錯。一個簡單的示例如下
#!/usr/bin/env python3
# coding:utf8
import argparse
parser = argparse.ArgumentParser(description='整數處理')
parser.add_argument('integers', type=int, help='要處理整數')
args = parser.parse_args()
print(args.integers)
這個示例的意思是接收一個數字,並將這個數字輸出,接下來看一下詳細的解釋
首先創建了一個ArgumentParser
對象,ArgumentParser
對象有很多參數可以選擇,這裏的description
定義在參數幫助文檔之前顯示的文本,通常用來定義這個程序做什麼以及怎麼做
parser = argparse.ArgumentParser(description='整數處理')
然後通過add_argument
給ArgumentParser
對象添加參數,第一個參數integers
爲參數名,type
指定類型爲int
,help
指定這個字段的幫助信息
parser.add_argument('integers', type=int, help='要處理的整數')
通過調用ArgumentParser
對象的parse_args
方法返回一個具有所有參數屬性的對象
args = parser.parse_args()
最後通過args.參數名
獲取到傳入的參數值
print(args.integers)
執行
我們將以上文件命名爲opscoffee
,並賦予執行權限,放在系統環境變量/bin
下,就可以當作命令直接執行了
# chmod +x opscoffee
# mv opscoffee /bin/
如果直接執行opscoffee
命令的話將會收到一個報錯,提示你必須有一個參數integers
# opscoffee
usage: opscoffee [-h] integers
opscoffee: error: the following arguments are required: integers
同時也通過usage
告訴了你這個命令的用法,默認有一個-h
參數可以打印命令幫助
# opscoffee -h
usage: opscoffee [-h] integers
整數處理
positional arguments:
integers 要處理的整數
optional arguments:
-h, --help show this help message and exit
這就是使用argparse
模塊的好處,自動生成幫助,提供友好的使用體驗
參數
argparse
能實現的遠不止於此,還有更加強大的功能,主要在於add_argument
方法參數的運用,以下以一些例子來學習下一些常用的參數
可選參數
當我們在參數名前添加-
或者--
時,argparse
會默認認爲這是一個可選參數,可以不傳值,例如
parser.add_argument('--age', type=int, help='年齡')
可選參數當沒有傳值時的默認值爲None
,可以通過default
來設置默認值
parser.add_argument('--age', type=int, default=37, help='年齡')
當沒有參數--age
時,顯示default
設置的值(沒有設置default
則顯示None),有--age
則顯示--age
指定的值
# opscoffee
37
# opscoffee --age 38
38
如果你想讓可選參數也變成必選的,則只需要設置required=True
即可
parser.add_argument('--age', type=int, default=37, required=True, help='年齡')
type
type
用來指定參數的類型,允許任何類型檢查和類型轉換,例如str
、int
、float
、open
甚至是你自定義的方法都可以。
#!/usr/bin/env python3
# coding:utf8
import argparse
def even(string):
value = int(string)
if value%2!=0:
msg = "%r 不是偶數" % string
raise argparse.ArgumentTypeError(msg)
return value
parser = argparse.ArgumentParser(description='整數處理')
parser.add_argument('integers', type=even, help='要處理的整數')
args = parser.parse_args()
print(args.integers)
以上命令接收一個參數integers
,並將其type
設置爲了自定義方法even
,這個方法會判斷用戶輸入的數字是否是偶數,如果不是則報錯,執行結果如下
# opscoffee 1
usage: opscoffee [-h] integers
opscoffee: error: argument integers: '1' 不是偶數
# opscoffee 2
2
choices
choices
參數可以限制參數的範圍,例如我們只想讓用戶從ops
或coffee
兩個參數中選擇一個輸入,則可以這樣用
parser.add_argument('site', choices=['ops','coffee'], help='Site')
那麼當我們輸入的內容不是ops
或coffee
時,則報錯
# opscoffee cn
usage: opscoffee [-h] {ops,coffee}
opscoffee: error: argument site: invalid choice: 'cn' (choose from 'ops', 'coffee')
nargs
通常我們在argparse
中定義的參數數量與傳入的參數數量應當相等,但有些時候我們需要接收未知數量的參數,nargs
就可以幫助我們
nargs支持以下值:N
(整數)、'?'
、'*'
、'+'
、argarse.REMAINDER
N:
表示N個參數會被聚集到一個列表中,例如
import argparse
parser = argparse.ArgumentParser(description='整數處理')
parser.add_argument('integers', type=int, nargs=2, help='要處理的整數')
args = parser.parse_args()
print(args.integers)
將輸出
# opscoffee 9 10
[9, 10]
'?':
表示使用一個或不使用參數,當不傳參數時,默認爲None
parser.add_argument('integers', type=int, nargs='?', help='要處理的整數')
'*':
表示使用所有參數,參數個數可以爲0
parser.add_argument('integers', type=int, nargs='*', help='要處理的整數')
'+':
與'*'
類似,但至少要有一個參數,否則將會報錯
parser.add_argument('integers', type=int, nargs='+', help='要處理的整數')
自定義命令
先說我爲什麼要寫個系統命令?
文章『Probius:一個功能強大的自定義任務系統』中介紹了我們的自定義任務系統,這個系統可以用來編排任務,而在編排CICD任務中會用到配置文件,我們的配置文件都是通過Kerrigan配置中心來管理的,目前獲取配置中心的配置主要有兩種方法
1. 配合confd服務自動拉取更新,詳細內容可以查看這篇文章:中小團隊落地配置中心詳解
2. 配置中心提供API,可以通過API獲取配置內容
腳本里如果想要使用配置中心的配置,則只能通過API的方式去獲取,這樣就要在每個需要用到配置的地方寫一段代碼來獲取及處理,不僅會出現大量的重複代碼,並且非常的不優雅,更爲重要的是請求API的Token將會出現在腳本里,帶來一定的安全風險
基於以上考慮,寫個自定義命令來做這件事情更爲妥當,於是便寫了下邊這個命令
#!/usr/bin/env python3
# coding:utf8
# 這是一個系統命令用來獲取kerrigan配置中心的配置並寫入本地文件,需要將此文件copy到目錄/bin下
import sys
import argparse
import requests
parser = argparse.ArgumentParser(description='獲取配置中心Kerrigan配置')
parser.add_argument('configkey', type=str, help='配置中心中文件的Key')
parser.add_argument('localfile', type=str, help='保存到本地文件的路徑')
args = parser.parse_args()
# 獲得傳入的參數
configkey = args.configkey
localfile = args.localfile
header = {
'Authorization': 'Token eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.JTdCJTIyZXhwJTIyJTNBMTkwNjcwNzAyMCUyQyUyMmlhdCUyMiUzQTE1OTEzNDcwMjAlMkMlMjJkYXRhJTIyJTNBJTdCJTIydXNlcm5hbWUlMjIlM0ElMjJwcm9iaXVzQG9wcy1jb2ZmZWUuY24lMjIlN0QlN0Q.ops1ZNhq19XSEL2PUo-iQqzbhimDnpFiYc_7EUXftF4'}
uri = 'http://kerrigan.ops-coffee.cn/api/config/?key=' + configkey
r = requests.get(uri, headers=header)
if r.json()['state']:
content = r.json()['message']['content']
try:
with open(localfile, 'w') as f:
f.write(content)
sys.exit(0)
except Exception as e:
print('write local file failed: ', str(e))
sys.exit(3)
else:
print('get config failed: ', r.json()['message'])
sys.exit(1)
以上代碼的意思是根據傳入的key和file路徑,去配置中心獲取對應配置文件的內容並寫入到本地file中。需要注意的是exit
,返回合適的退出狀態是個很好的習慣,這樣我們就可以通過$?
來獲取命令執行成功還是失敗
將此文件命名爲getconfig並移動到/bin目錄下添加執行權限,就可以在系統任何地方使用getconfig
命令了
# getconfig /conf/coffee/prod/docker/Dockerfile /home/project/coffee/Dockerfile