利用argparse模块开发带复杂参数的命令行程序
背景
最近看了一些 ecFlow 的文档,与目前我们正在用的 SMS 相比有不少优点,其中一点就是 ecFlow 提供一个统一的客户端命令行程序 ecflow_client,替代之前的一组 sms 命令
smsinit smscomplete smsabort ...
和 cdp 命令接口。之前的用过 cdp 接口,感觉虽然比 ecflow_client 的命令短,但很难在线获得帮助信息,有时解析失效,不太好用。ecflow_client 尽管需要输入更多的信息,但胜在有详细的帮助信息,总体上来说更加方便易用。
ecflow_client 类似 git 等命令行程序,将多个子命令组合到一起,通过选项区分。
git 命令举例:
git add git clone git commit ...
ecflow_client 命令举例:
ecflow_client --init ecflow_client --complete ecflow_client --abort ...
我对这种命令行程序设计方式很感兴趣,就想到模拟 ecflow_client,为 SMS 创建一个简单的客户端接口,实现我平时用到的几个 cdp 命令功能:加载 def 文件、替换节点、删除节点。
需求
一个客户端程序,根据选项实现不同的子命令功能,每个子命令可能有不同的命令行参数,也有通用的命令行参数。
sms_client load sms_client replace sms_client delete
实现
Python 的 argparse 模块提供构建复杂命令行参数的功能,可以满足上述需求。
通用参数
SMS 客户端命令需要登录到 SMS 服务器后才能运行,所以所有的子命令都需要 SMS 服务器主机名和用户名这两个参数。
argparse 提供共享参数解析器的机制,在创建解析器时可以设置 parents 解析器列表,列表中每个解析器的参数都会添加到新创建的解析器中。所以先创建主机和用户名的解析器。
login_parser = argparse.ArgumentParser(add_help=False)
login_parser.add_argument("-n", "--name", help="sms host name")
login_parser.add_argument("-u", "--user", help="sms user name")注意,不要引入重复参数,所以要设置 {py}add_help=False{/py},防止通用解析器自动添加 help 参数。
子命令
sms_client 以不同的子命令提供不同的功能,argparse 模块通过 ArgumentParser.add_subparsers 支持子命令。
创建 sms_client 的参数解析器
parser = argparse.ArgumentParser(description="SMS client tool.")
添加子命令解析器
subparsers = parser.add_subparsers(title='sub commands', dest='sub_command')
用户选用某种子命令后,该子命令名会保存在 dest 属性设置的变量中,也就是 sub_command 变量中,后续程序可以根据 sub_command 的值执行不同的操作。
添加不同的子命令
# delete
parser_delete = subparsers.add_parser('delete',
parents=[login_parser],
description="Delete node(s) given. Same to cancel(cdp) in sms."
"By default, user -y option in cdp commands.")
parser_delete.add_argument('node', help='The name of the node to be deleted')
# load
parser_load = subparsers.add_parser("load",
parents=[login_parser],
description="Define, validate and send the suites to the SMS. "
"Same to play(cdp) in sms.")
parser_load.add_argument('def_file', help='The name of the file that contain the definitions.')
# replace
parser_replace = subparsers.add_parser("replace",
parents=[login_parser],
description="Replace the node given in the SMS")
parser_replace.add_argument('node',
help='path to node. must exist in the client definition. '
'This is also the node we want to replace in the server')
parser_replace.add_argument('def_file', help='path to client definition file. '
'provides the definition of the new node')参数解析
使用 parse_args() 解析参数,返回解析后的结果。
args = parser.parse_args()
根据子命令执行对应的操作
if args.sub_command == 'delete':
delete_handler(args.node, name=args.name, user=args.user)
elif args.sub_command == 'load':
load_handler(args.def_file, name=args.name, user=args.user)
elif args.sub_command == 'replace':
replace_handler(args.node, args.def_file, name=args.name, user=args.user)运行示例
命令帮助
$ sms_client.py --help
usage: sms_client.py [-h] {delete,load,replace} ...
SMS client tool.
optional arguments:
-h, --help show this help message and exit
sub commands:
{delete,load,replace}子命令帮助
$ sms_client.py replace --help
usage: sms_client.py replace [-h] [-n NAME] [-u USER] node def_file
Replace the node given in the SMS
positional arguments:
node path to node. must exist in the client definition.
This is also the node we want to replace in the server
def_file path to client definition file. provides the
definition of the new node
optional arguments:
-h, --help show this help message and exit
-n NAME, --name NAME sms host name
-u USER, --user USER sms user name