設計 Command line Interface 的三個 Pattern

VT100 serial terminal

在我們討論 User Interface 的時候,通常討論的是 Graph User Interface (GUI)。在我們創(chuàng)建 Command line 工具的時候,好的 User Interface 可以讓工具具有很好的用戶體驗。這種交互方式稱為 Command line Interface。比如 Git 就是一個非常優(yōu)秀的 Command line Interface 案例。

本文將介紹設計 Command line Interface 的三個 Pattern。

1. Command 名稱即功能

這是最簡單的一種 Command line,這種 Command line 沒有參數(shù),通常名稱即 Command 功能。比如 install.sh。通常我會用這種方式簡化重復的,或者復雜參數(shù)的指令。

比如,使用 Docker 啟動 mysql 數(shù)據(jù)庫,我會在項目目錄下創(chuàng)建一個腳本 run-mysql,它需要包含如下內(nèi)容:

  • Command 文件
  • 具有可執(zhí)行權限

run_mysql.sh:

#!/usr/bin/env sh

docker-machine start dev
eval "$(docker-machine env dev)"

docker run --rm -p 3306:3306 --name mysql-5.6 -e MYSQL_ROOT_PASSWORD=root mysql:5.6 --lower-case-table-names=1

賦予執(zhí)行權限:

?  shell ? chmod +x run-mysql.sh
?  shell ? mv run-mysql.sh run-mysql #去掉 .sh 后綴

使用:

?  shell ? ./run-mysql
Starting "dev"...
Machine "dev" is already running.
Initializing database

在我們創(chuàng)建 run-mysql.sh 時,默認情況下它是沒有執(zhí)行權限的,需要使用 chmod +x run-mysql.sh 來賦予執(zhí)行權限。

2. 帶參數(shù)的 Command

通常 Command 需要接收參數(shù),比如: curl http://www.google.com/。這類 Command 會有點復雜度,需要包含如下部分內(nèi)容:

  • 使用說明,說明參數(shù)的意義
  • 對參數(shù)做校驗
  • 處理參數(shù)

例如,我們把上面的腳本做下修改,讓他支持 version 這個參數(shù)。

#!/usr/bin/env sh

function usage() {
cat <<EOF
Usage:
    run-mysql <version>

Description:
    Run mysql in docker container with specific version.

EOF
  exit 0
}

if [ $# == 0 ]; then
  usage
fi

MYSQL_VERSION=$1
echo "Run mysql:$MYSQL_VERSION via docker ..."

docker-machine start dev
eval "$(docker-machine env dev)"

docker run --rm -p 3306:3306 --name mysql-$MYSQL_VERSION -e MYSQL_ROOT_PASSWORD=root mysql:$MYSQL_VERSION --lower-case-table-names=1

默認輸出 Usage:

?  shell ? ./run-mysql
Usage:
    run-mysql <version>

Description:
    Run mysql in docker container with specific version.

提供 version 參數(shù):

?  shell ? ./run-mysql 5.6
Run mysql:5.6 via docker ...
Starting "dev"...
Machine "dev" is already running.
Initializing database

3. Git-like Command line

用過 Git 的小伙伴應該都比較熟悉這種方式,它采用通常 Command + <Sub-Command> + [ARGS] 的方式來組織 Interface。它更適合更多的指令。現(xiàn)代的,大多數(shù)流行的 Command line 都采用這種方式,比如 Gem, Rails, Gradle 等。 此時 Command line 需要包含如下內(nèi)容:

  • 使用說明,說明 Sub-Command 和參數(shù)的意義
  • 對參數(shù)做校驗
  • 處理 Sub-Command
  • 在 Sub-Command 中處理參數(shù)

我們對上面的 run-mysql 再進行改造,讓它支持 runkill 兩個 Sub-Command

例如:

我們將腳本改為 mysql-docker:

?  shell ? ./mysql-docker
Usage:
    mysql-docker <command> <version>

Commands:
    run <version>         Run mysql in docker container with specific version.
    kill <version>        Kill mysql:<version>

啟動 mysql 5.6:

?  shell ? ./mysql-docker run 5.6
Starting "dev"...
Machine "dev" is already running.
Run mysql:5.6 via docker ...
Initializing database

Kill mysql 5.6:

?  shell ? ./mysql-docker kill 5.6
Starting "dev"...
Machine "dev" is already running.
Kill mysql 5.6 ...
mysql-5.6

腳本:

#!/usr/bin/env sh

function usage() {
cat <<EOF
Usage:
    mysql-docker <command> <version>

Commands:
    run <version>         Run mysql in docker container with specific version.
    kill <version>        Kill mysql:<version>

EOF
  exit 0
}

if [ $# != 2 ]; then
  usage
fi

docker-machine start dev
eval "$(docker-machine env dev)"

function run() {
  local version=$1
  echo "Run mysql:$version via docker ..."
  docker run --rm -p 3306:3306 --name mysql-$version -e MYSQL_ROOT_PASSWORD=root mysql:$version --lower-case-table-names=1
}

function kill() {
  local version=$1
  echo "Kill mysql $version ..."
  docker kill mysql-$version
}

$@

此時 $@ 用來處理 Sub-command。它用來獲取 Command 所有參數(shù)。當我們執(zhí)行 ./mysql-docker run 5.6 時, $@run 5.6。Shell script 按照指令展開來解析 run 5.6,此時會調(diào)用 run 方法,并且將 5.6 作為參數(shù)。

實踐應用

DRY 一直是我們追求的目標,對于像我這么懶惰的 Developer,如果能自動化的事情,我一定不會用手動的方式重復去做。平時在項目中我會創(chuàng)建一些方便的腳本自動化重復的工作。這些腳本會遵循本文提到的 Pattern。

1. Command 名稱即功能

./bin/run.sh,這個腳本在所有的 api service 代碼庫中都有。Dockerfile 中調(diào)用它來啟動 service:

Dockerfile:

FROM ubuntu-ruby2.3:latest
# setup environment
CMD ["bin/run"]

此時 Dockerfile 并不需要關心如何啟動 service,./bin/run.sh 解耦了 service 運行步驟。

2. 帶參數(shù)的 Command

ssh_ec2 用來鏈接 AWS EC2 Instance:

?  shell ? ssh_ec2

  Usage:
      ssh_ec2 [INSTANCE_NAME ...] -- ssh to ec2 instances

  Samples:
      ssh_ec2 service-name        -- ssh to service-name ec2 instance

3. Git-like Command line

這類 Command line Interface,非常具有描述性,也是我用的最多的一種。比如:

通過 AWS Auto Scaling 來手動控制 ETL service:

?  etl-service git:(master) ./bin/etl
Usage:
    ./etl <start|stop> <test|prod>

Commands:
    start         Run etl service
    stop          Terminate etl service ec2 instance

通過 AWS Cloudformation 來創(chuàng)建 SNSSQS 基礎設施:

?  aws-queues git:(master) ./stack
Usage:
    ./stack <command> [ARG]

Commands:
    create [test|prod] [STACK-NAME ...]     Create SNS and SQS stack
    update [test|prod] [STACK-NAME ...]     Update SNS and SQS stack, only avaiable for updating policy
    delete [test|prod] [STACK-NAME ...]     Delete SNS and SQS stack

總結

一個好的 Command line 應該易于理解和使用。按照本文提到的三個 Pattern 可以幫助你設計一個易用的 Command line Interface。

引用一段來自程序員的搞笑注釋以供自勉:

// 寫這段代碼的時候,只有上帝和我知道它是干嘛的
// 現(xiàn)在只有上帝知道

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,443評論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,530評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,407評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,981評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,759評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,204評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,263評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,415評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,955評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,650評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,892評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,675評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374

推薦閱讀更多精彩內(nèi)容