在我們討論 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
再進行改造,讓它支持 run
和 kill
兩個 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)建 SNS 和 SQS 基礎設施:
? 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)在只有上帝知道