使用 Python 来做一些运维类的脚本工作的时候,少不了要读取命令行参数来控制脚本的工作逻辑。一般情况下我们直接的使用 sys
模块的 sys.argv
就可以得到命令行参数的列表(其中 sys.argv[0]
是脚本名)。
但是用 sys
不好的地方在于不够灵活,对于多个参数只能依顺序传入,可以说是严格按照 位置 来确定参数的。当然啦如果是配合管道或者 xargs
使用其实也很合适,但没有办法按照 命名 来指定参数确实比较原始。
这里所说的命令行的命名参数形如
-o output.txt
,其中-o
是命名参数中的命名(也可以理解为指定),output.txt
是命名参数中的参数(这个不一定需要)。
Python 在标准库中也包含了两个相关的库 getopt
以及 argparse
(optparse
在 2.7 后已经被弃用),但是基本上找到的说明都比较模糊,用法不太明确,所以在这里简单 mark 下自己过去的用法以备查看。
一、 getopt
getopt
模块是 python 的标准库之一,对于位置参数可以简单的返回参数列表,它的改进是可以简单地解析带 -
和 --
格式的参数。
其核心使用函数如下:
1 | getopt.getopt(args, options[, long_options]) |
这个函数主要是创建一个解释器,然后套用到命令行参数上解释它。根据 help
方法的解释,其中:
参数 | 说明 |
---|---|
args | 指定需要被解析的参数列表,通常情况下,它意味着 sys.argv[1:] |
shortopts | 是由那些想要被脚本识别的可选字母(可选字母作为命名参数的命名)组成的字符串,如 -o 中的 o 或 -v 中的 v 。对于需要在 命令行参数 中实现 -o <outputfile> 这样字母选项后面跟随入参的,要在 定义 的字母后面加上 : ,如 o: ;这个格式和 Unix 下的 getopt() 函数是一致的 |
long_options | 是可选的,它是为了提供形如 -- 开头的字符串作为命名参数命名的支持。long_options 是一个字符串列表。类似于 shortopts ,如果需要在命名后跟随入参的,要在 定义 的字符串后面加上 = |
函数返回两个值,其中:
- 第一个是由形如
(option, value)
形式组成的元组列表;其中返回的option
是带有前缀-
或--
的;value
的值为字符串,也有可能为空字符串(对于命名参数没有入参的情况)。 - 第二个是把可以识别的命名参数(选项型参数)剥离出来之后剩下的参数列表
1. 实例
1 | # coding: utf-8 |
在 shell 中运行以下命令:
1 | # 1. 查看帮助信息 |
2. 稍微总结一下
可以看到 getopt
的使用相当简洁。但问题是我们要特别留心处理额外的情况;其次,我们对于参数的用途以及命令用法等基本上都需要自己手动给出提示信息和说明,搬砖工作,不太优雅。可想而知作为参数解释器来说,getopt
还是比较简陋的。因此我们有别的选择:argparse
二、 argparse
argparse
模块也是由 Python 标准库中提供出来的,同样它的主要用法也是一个解释器,去解释给到的 args
命令行参数。听上去和 getopt
没有区别啊,也是定义一个解释器(就是自己定义一些解析规则)去套命令行参数就完事了。但是我们看一下模块介绍就觉得不一般:
1 | This module is an optparse-inspired command-line parsing library that:(是基于 optparse 的命令行解释库) |
哦,好像把之前 getopt
总结出来的不足都解决掉了,那么它到底是怎么做到的呢?关键在于,它的这个解释器的定义,就远比 getopt
定义的复杂的得多。
1. ArgumentParser
一开始,使用 argparse
也必须定义出一个解释器,使用 argparse.ArgumentParser
方法,得到一个用于将命令行参数字符串转变为 Python 对象的对象(是有点拗口)。
1 | argparse.ArgumentParser(prog=None, usage=None, description=None, epilog=None, parents=[], formatter_class=<class 'argparse.HelpFormatter'>, prefix_chars='-', fromfile_prefix_chars=None, argument_default=None, conflict_handler='error', add_help=True, allow_abbrev=True) |
简直让人头皮发麻的入参列表……但是我们一般用不着指定那么多,大部分人都是无参调用,充其量指定一下 description
(描述一下这个脚本或程序是干什么的) 和 usage
(使用说明,但一般不会在这里写而是在具体选项定义),奇葩一点的操作大概是改一下 prog
(程序名,默认是取 sys.argv[0]
)或者 prefix_chars
(默认的命名参数的命名都以 -
开头)。
到此为止我们也只是定义了一个只有描述的解释器,那么怎么定义我们的解释规则呢?重点来了,add_argument
函数 ——
2. add_argument
先从定义入手:
1 | # add_argument 的 signature 信息 |
help
得到的看上去平平无奇还有些简陋,不怕,我们去看文档库,果然得到的 signature 信息就具体多了:
1 | ArgumentParser.add_argument(name or flags...[, action][, nargs][, const][, default][, type][, choices][, required][, help][, metavar][, dest]) |
这里的话,不得不慢慢说一下这些参数代表的含义了:
参数 | 说明 |
---|---|
name or flags |
指定这个命名参数(或选项型参数)的命名或者标识 (Either a name or a list of option strings, e.g. foo or -f, –foo.) |
action |
指定一些基本的 action 来处理这个参数 (The basic type of action to be taken when this argument is encountered at the command line.) |
nargs |
一个数值,用来指定命名参数的命名后面跟随的 nargs 个字符串作为命名参数的值看待 (The number of command-line arguments that should be consumed.) |
const |
为 action 或者 nargs 选择项保留一个常量(A constant value required by some action and nargs selections.) |
default |
如果这个命名参数的值为空,使用这个默认值 (The value produced if the argument is absent from the command line.) |
type |
指定这个命令行参数的值的处理类型 (The type to which the command-line argument should be converted.) |
choices |
这个参数的可选值容器,可以理解为枚举列表啦 (A container of the allowable values for the argument.) |
required |
指定这个参数是否是不可省略的 —— 仅针对命名参数而言 (Whether or not the command-line option may be omitted (optionals only).) |
help |
一个关于这个参数是干什么用的基本描述 (A brief description of what the argument does.) |
metavar |
指定参数在帮助信息中要显示的名称 (A name for the argument in usage messages.) |
dest |
指定通过 parse_args 获取值的时候所要使用的名称 (The name of the attribute to be added to the object returned by parse_args().) |
大部分的内容都可以从这份参数解释表得出,我们尝试利用我们的理解去实现一些例子 ——
① 示例:指定位置参数
1 | parser = argparse.ArgumentParser() |
如上所示,对于不是以 -
前缀开头的所有的 name
or flag
,ArgumentParser 都默认的把他们当成 位置参数 依次对应赋值。
② 示例:指定命名参数
1 | parser = argparse.ArgumentParser() |
定义了如上所示的一个解释器,让我们去执行一下这个命令行解释器:
1 | # 1. 打印帮助信息 |
注意:
- 对于之前那种命名参数类型只有命名没有值的情况(即如
add_argument("-v", "--version")
这种),当命令行中没有对应的入参,在 Namespance 上反馈的是version=None
。如果像上面那样定义了action="store_true"
,则会改为以 bool 类型来表示。 choices
可以提供一个可选值的列表,如果对应的命名参数类型的值不在列表中就会报错,弹出帮助提示(比getopt
省心)default
可以指定默认值(针对命名参数类型而言)。nargs
的值可以是整数,也可以是?
、*
和+
,类似于正则表达式,?
表示匹配后面 0 到 1 个字符串作为命名参数的值,*
表示匹配命名后面 0 到 n 个字符串作为命名参数的值,+
表示匹配后面 1 到 n 个字符串作为命名参数的值。
③ 互斥参数实现
使用 ArgumentParser.add_mutually_exclusive_group
可以实现一组互斥的入参,具体如下所示:
1 | parser = argparse.ArgumentParser() |
组 group 内不管加入多少个参数 add_argument
,都只能出现一个,否则就会报错,弹出帮助提示。
④ action 是什么
action
参数主要时 ArgumentParser 定义时指定对特定的命令行参数要执行什么样的处理,尽管大多数情况下它们都只是简单地为 parse_args
返回的 Namespace
对象上加一个属性。内置支持的 action 有以下几种:
action | 说明 |
---|---|
store | 存储起命名参数的值,这是 默认 的 action |
store_const | 同样时存储起命名参数的值,但是值是由 const 参数指定的 |
store_true/store_false | 以 bool 类型存储命名参数的值 |
append | 存储的是一个列表,它们作为值都属于同一个命名参数,常用于命令行有重复指定参数的情况:parser.parse_args('--foo 1 --foo 2'.split()) |
count | 存储的命名参数的值是命名出现的次数,例如 parser.parse_args(['-vvv']) 得到的值会是 3 |
help | 使得命名参数具有和 -h 默认的行为,下一节会提到 |
version | 通常和 version= 参数搭配使用,当命令行参数出现对应的命名时,打印 version 的值 |
更多关于 action 的信息以及如何自定义 action,可以参考官方文档
⑤ 默认的 -h
行为
在 argparse 中,-h
命名参数具有特殊的含义:用于打印帮助消息并退出程序(执行 sys.exit()
)。这个 -h
的行为不可更改,如果想要控制使用 -h
显示帮助同时不退出交互界面,我们可以在程序中捕获 SystemExit
异常:
1 | # 定义命令行参数解释器 |
※ 题外话
这里 有一篇在 docs python 上的文章告诉你怎么使用
argparse
,更具体来说,就是告诉你add_argument
怎么配置参数达到你想要的效果。有兴趣的请自行食用😄
3. parse_args
通过上面的示例可以看出,如果说 argparse.ArgumentParser.add_argument
是用来定义解释器的,那么 parse_args
就是用来对参数执行解释器的。它的 signature 如下所示:
1 | ArgumentParser.parse_args(args=None, namespace=None) |
其中:
- args —— 需要解释的参数字符串列表,默认是从
sys.argv
中取得 - namespace ——
Namespace
对象,用以对参数划分命名空间 ,默认是一个空的Namespace
对象。