python製做命令行工具——fire

前言node

本篇教程的目的是但願你們能夠通讀完此篇以後,可使用python製做一款符合本身需求的linux工具。python

本教程使用的是google開源的python第三方庫:firelinux

不管是學生黨本身作着練手,仍是工做中確有需求,本篇都儘量經過簡單的例子來示範該第三方庫的用法,其中如有描述不當的地方,望留言指出。git


1、快速介紹

來一波官方介紹。github

  • Python Fire是一個庫,用於從任何Python對象自動生成命令行接口。
  • 是用python建立CLI的一種簡單方法。
  • 是開發和調試Python代碼的一個有用工具。
  • Python Fire幫助探索現有代碼或將其餘人的代碼轉換爲CLI。
  • 使得Bash和Python之間的轉換更加容易。
  • 經過使用已經導入和建立的模塊和變量來設置REPL, Python Fire使使用Python REPL變得更容易。

沒聽懂 ???shell

不是太明白 ???express

沒關係,看完本篇就懂了。canvas


2、快速安裝

  • pip安裝:pip install fire
  • conda安裝:conda install fire -c conda-forge
  • 源碼安裝:
1. git clone https://github.com/google/python-fire.git
2. cd python-fire
3. python setup.py install

Github地址:python-firebash


3、快速上手

實踐出真知服務器

建立一個test.py文件,寫入如下內容

import fire

def test(your_var="default_value"):
	return 'This is a test ! value : %s' % your_var

if __name__ == '__main__':
    fire.Fire(test)

我們來看一下效果

# 缺省參數
root@node:~# python test.py 
This is a test ! value : default_value

# 關鍵字參數
root@node:~# python test.py --your_var=newValue
This is a test ! value : newValue

# 位置參數
root@node:~# python test.py localtionValue
This is a test ! value : localtionValue

如今呢,咱們反過頭來看一下官方介紹的第一行:

Python Fire是一個庫,用於從任何Python對象自動生成命令行接口。

注意關鍵字:任何python對象。這意味着什麼?

咱們來看一段代碼:

import fire

boy_name = 'XiaoMing'
girl_name = 'XiaoHong'

if __name__ == '__main__':
  fire.Fire()

試一下:python test.py boy_name

是否是明白了些什麼。

聊完這缺省參數關鍵字參數位置參數,固然不能少了 *args 和 ** kwargs .

仍是來看代碼示例:

import fire

def run(*args):
    arg_list = list(args)
    return ' | '.join(arg_list)

if __name__ == '__main__':
  fire.Fire(run)

跑一下就懂啦

root@node:~# python test.py run qwe rty uio asd fgh
qwe | rty | uio | asd | fgh

官方給的示例是這個樣子的~~~

import fire

def order_by_length(*items):
  """Orders items by length, breaking ties alphabetically."""
  sorted_items = sorted(items, key=lambda item: (len(str(item)), str(item)))
  return ' '.join(sorted_items)

if __name__ == '__main__':
  fire.Fire(order_by_length)

就是加了個長度和字母順序的排序,來跑一下,看一下效果:

$ python example.py dog cat elephant
cat dog elephant

除此以外呢,咱們還能夠給輸出結果加點料,仍是剛纔咱們寫的那個例子:

root@node:~# python test.py run qwe rty uio asd fgh - upper
QWE | RTY | UIO | ASD | FGH

在這裏,咱們經過命令行對傳入的對象和調用結果執行相同的操做,譬如這裏的 upper

敲黑板劃重點:分隔符 「 - 」 以後的全部參數都將用於處理函數的結果,而不是傳遞給函數自己。默認的分隔符是連字符 「 - 」。

默認的分隔符也是能夠改的,用到了fire的內置參數。

root@node:~# python test.py run qwe rty uio asd fgh X upper -- --separator=X
QWE | RTY | UIO | ASD | FGH

其中的separator就是fire的一個內置參數,更多內置參數文末有提到。

咱們再來看一下fire給咱們提供的命令行傳參時,數據的類型。比較特殊的是,fire根據值決定類型。

import fire
fire.Fire(lambda obj: type(obj).__name__)

若是有剛學python的小夥伴,記得必定要學一下lambda函數,在這裏我能夠轉化爲普通寫法。

import fire

def test(obj):
    return type(obj).__name__

if __name__ == '__main__':
    fire.Fire(test)

經過簡單的一行代碼來看一下各類數據類型如何經過命令行傳參:

$ python example.py 10
int
$ python example.py 10.0
float
$ python example.py hello
str
$ python example.py '(1,2)'
tuple
$ python example.py [1,2]
list
$ python example.py True
bool
$ python example.py {name:David}
dict

可是當你想傳遞一個str類型的10,你就要注意了,看如下例子:

$ python example.py 10
int
$ python example.py "10"
int
$ python example.py '"10"'
str
$ python example.py "'10'"
str
$ python example.py \"10\"
str

咱們能夠看到,你雖然敲了"10",可是依然被斷定爲int,bash會自動處理掉你參數的第一層引號。因此,若是想傳str類型的10,要再加一層引號,單雙引號分開用,或者把引號轉義。

若是要傳的是dict參數,那就更要當心謹慎了。

# 標準寫法
$ python example.py '{"name": "David Bieber"}' 
dict
# 要這麼寫也沒啥問題
$ python example.py {"name":'"David Bieber"'} 
dict
# 但要這麼寫就解析成了字符串了
$ python example.py {"name":"David Bieber"}
str
# 再加個空格,字符串都不是了
$ python example.py {"name": "David Bieber"}  # Wrong. This isn't even treated as a single argument.
<error>

到這裏,我想你們應該大概明白了 fire 的方便快捷之處。

到了這一步的時候,雖然實現了基本功能,但仍是和平時咱們使用的 linux 命令行工具備很大的區別:

  1. 每次跑命令都要再敲一個python

  2. 每次還要指向指定的py文件或到指定的目錄下

    首先說第一個問題,每次多敲六個字母和一個空格,做爲一個linux命令行工具是很是不合格的,原本命令行工具就在追求簡單化,這種指定解釋器的操做咱們固然要儘量省掉咯

    第二個問題,總是指定文件的目錄就更麻煩了,平常使用的時候在不一樣的服務器跑命令還要想一想放在哪裏,並且若是使用絕對路徑的話,更會致使命令的冗長。

下面咱們來解決一下這兩個「小」問題:

  1. 在文件的第一行指定python解釋器,這樣就無需在咱們運行該文件時再指定解釋器
#!/usr/bin/python

import fire

def test(your_var="default_value"):
	return 'This is a test ! value : %s' % your_var

if __name__ == '__main__':
    fire.Fire(test)
  1. 增長文件的可執行權限
root@node:~# chmod +x test.py
  1. 美化如下,去掉小尾巴(僅僅是給文件改了個名字, 這一步非必須)
root@node:~# mv test.py mytool
  1. 作個軟鏈接,能夠隨時隨地找獲得該命令
root@node:~# ln -s /root/mytool /usr/bin/mytool

附:若是須要指定編碼的話,能夠在文件頭部加一行,好比

#!/usr/bin/python
# coding: utf-8

這個時候,咱們隨意在服務器的任意位置執行

root@node:~# mytool
This is a test ! value : default_value

root@node:~# mytool --your_var=newValue
This is a test ! value : newValue

root@node:~# mytool localtionValue
This is a test ! value : localtionValue

Perfection !

若是你已經走到這一步的話,其實已經能寫不少簡單的命令行工具了。

爲何說簡單呢,目前都是使用函數來完成一個個命令的邏輯,多一個子命令多寫一個函數,慢慢的就會讓這個文件變的龐雜和冗餘。並且長此以往,確定會出現一些看起來很類似,卻要使用ctrl + c-v大法去完成的事情。甚至有一些邏輯,想要實現還要本身去作更復雜的邏輯。


4、快速進階

此時,一年級的已經能夠下課了,二年級的請注意聽講了,下面,咱們要講的是:

類的使用

命令嵌套

屬性訪問

鏈式調用

4.1 類的使用

經過一個簡單的算數類來了解其用法,在下列用法中,咱們在fire中註冊了一個類對象。

import fire

class Calculator(object):

  def add(self, x, y):
    return x + y

  def multiply(self, x, y):
    return x * y

if __name__ == '__main__':
  calculator = Calculator()
  fire.Fire(calculator)

如下是調用測試

$ python example.py add 10 20
30
$ python example.py multiply 10 20
200

固然咱們也能夠註冊一個類。

import fire

class Calculator(object):

  def add(self, x, y):
    return x + y

  def multiply(self, x, y):
    return x * y

if __name__ == '__main__':
  fire.Fire(Calculator)

跑一下看看:

$ python example.py add 10 20
30
$ python example.py multiply 10 20
200

就這?固然不會,咱們還能夠經過參數控制實例屬性,就像下面的例子:

import fire

class BrokenCalculator(object):

  def __init__(self, offset=1):
      self._offset = offset

  def add(self, x, y):
    return x + y + self._offset

  def multiply(self, x, y):
    return x * y + self._offset

if __name__ == '__main__':
  fire.Fire(BrokenCalculator)

咱們能夠看到,新增了一個offset的實例屬性,缺省值是1.

$ python example.py add 10 20
31
$ python example.py multiply 10 20
201

重點來了,咱們能夠直接給屬性賦值,以此來增長你命令行工具的自由度。

$ python example.py add 10 20 --offset=0
30
$ python example.py multiply 10 20 --offset=0
200

4.2 命令嵌套

經過不一樣的類來控制某些同名命令,其實也是將各個命令分門別類更具條理性的管理。能夠看到如下用法。

import fire

class Sing:
    def run(self):
        print('sing sing sing ...')

class Dance:
    def run(self):
        print('dance dance dance ...')

    def status(self):
        print('Around.')


class Pipeline:
    def __init__(self):
        self.sing = Sing()
        self.dance = Dance()

    def run(self):
        self.sing.run()
        self.dance.run()
        self.dance.status()

if __name__ == '__main__':
    fire.Fire(Pipeline)

跑跑看:

# python3 ball.py run
sing sing sing ...
dance dance dance ...
Around.
# python3 ball.py sing run
sing sing sing ...
# python3 ball.py dance run
dance dance dance ...
# python3 ball.py dance status
Around.

根據自定義的一個Pipeline類,咱們能夠本身組合想要的命令行效果,給子命令再分配不一樣的子集。

4.3 屬性訪問

其實前面說到類的時候已經簡單的說過屬性訪問(就是那個offset的例子,行啦,忘了就不用往上翻了),這裏再詳細舉例說明一下。

# python3 test.py --age=6 outinfo
Xiao Ming is 6 years old and in the First grade

# python3 test.py --age=7 outinfo
Xiao Ming is 7 years old and in the Second grade

# python3 test.py --age=8 outinfo
Xiao Ming is 8 years old and in the Third grade

綜上,咱們能夠經過控制類的屬性來構造類對象。

嘮到這兒了,再嘮一個騷操做

4.4 鏈式調用

官方給的例子不太好看,沒有那麼讓人一眼就看懂的感受,找了個四則運算的簡單示例:

import fire

class Calculator:

  def __init__(self):
    self.result = 0
    self.express = '0'

  def __str__(self):
    return f'{self.express} = {self.result}'

  def add(self, x):
    self.result += x
    self.express = f'{self.express}+{x}'
    return self

  def sub(self, x):
    self.result -= x
    self.express = f'{self.express}-{x}'
    return self

  def mul(self, x):
    self.result *= x
    self.express = f'({self.express})*{x}'
    return self

  def div(self, x):
    self.result /= x
    self.express = f'({self.express})/{x}'
    return self

if __name__ == '__main__':
  fire.Fire(Calculator)

函數名呢,addsubmuldiv分別對應 加、減、乘、除四則運算,每一個方法都接受 x 參數去運算,返回self,這樣不論日後鏈式調用多少次均可以,結束調用到 __str__ 打印出結果。

__str__fire 中用來完成自定義序列化。若是不提供這個方法,在鏈式調用完成後將會打印幫助內容。

# python3 test.py add 2 sub 1.5 mul 3 div 2
((0+2-1.5)*3)/2 = 0.75

# python3 test.py add 4 sub 2.5 mul 2 div 4 mul 3 sub 5 add 2
(((0+4-2.5)*2)/4)*3-5+2 = -0.75

看完這個你們應該明白鏈式調用的運用了,這個時候再來看一下官方示例也許會輕鬆一些。

import fire

class BinaryCanvas(object):
  """A canvas with which to make binary art, one bit at a time."""

  def __init__(self, size=10):
    self.pixels = [[0] * size for _ in range(size)]
    self._size = size
    self._row = 0  # The row of the cursor.
    self._col = 0  # The column of the cursor.

  def __str__(self):
    return '\n'.join(' '.join(str(pixel) for pixel in row) for row in self.pixels)

  def show(self):
    print(self)
    return self

  def move(self, row, col):
    self._row = row % self._size
    self._col = col % self._size
    return self

  def on(self):
    return self.set(1)

  def off(self):
    return self.set(0)

  def set(self, value):
    self.pixels[self._row][self._col] = value
    return self

if __name__ == '__main__':
  fire.Fire(BinaryCanvas)

跑一下看看:

$ python example.py move 3 3 on move 3 6 on move 6 3 on move 6 6 on move 7 4 on move 7 5 on
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 1 0 0 1 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 1 0 0 1 0 0 0
0 0 0 0 1 1 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0

PS:我要不說,誰能看出來這是個笑臉???


最後一課

最後看看官方給出的 fire 的內置參數吧,具體怎麼應用你們就本身研究咯。

Flags
Using a CLI Command Notes
Help command -- --help 顯示命令的幫助和使用信息。
REPL command -- --interactive 進入交互模式。
Separator command -- --separator=X 這將分隔符設置爲' X '。默認分隔符是「-」。
Completion command -- --completion [shell] 爲CLI生成一個補全的shell腳本。
Trace command -- --trace 跟蹤fire命令調用後發生了啥子。
Verbose command -- --verbose 在輸出中包含私有成員。

fire-GitHub地址

相關文章
相關標籤/搜索