10.6. 处理命令行参数

Python 完全支持创建可在命令行上运行的程序,并带有完整的命令行参数以及用于指定各种选项的短格式或长格式标志。这些内容都与 XML 无关,但此脚本充分利用了命令行处理,因此现在似乎是提及它的好时机。

如果不了解如何将命令行参数传递给 Python 程序,就很难讨论命令行处理,因此让我们编写一个简单的程序来查看它们。

示例 10.20. 介绍 sys.argv

如果您还没有这样做,则可以 下载本书中使用的此示例和其他示例

#argecho.py
import sys

for arg in sys.argv: 1
    print arg
1 传递给程序的每个命令行参数都将位于 sys.argv 中,它只是一个列表。在这里,您将在单独的行上打印每个参数。

示例 10.21. sys.argv 的内容

[you@localhost py]$ python argecho.py             1
argecho.py
[you@localhost py]$ python argecho.py abc def     2
argecho.py
abc
def
[you@localhost py]$ python argecho.py --help      3
argecho.py
--help
[you@localhost py]$ python argecho.py -m kant.xml 4
argecho.py
-m
kant.xml
1 关于 sys.argv,首先要知道的是它包含您正在调用的脚本的名称。您实际上将在稍后的 第 16 章“函数式编程” 中利用此知识。现在不用担心。
2 命令行参数由空格分隔,并且每个参数在 sys.argv 列表中显示为单独的元素。
3 命令行标志(如 --help)也在 sys.argv 列表中显示为其自己的元素。
4 更有趣的是,某些命令行标志本身也带有参数。例如,这里有一个标志 (-m),它带有一个参数 (kant.xml)。标志本身和标志的参数都只是 sys.argv 列表中的顺序元素。没有尝试将两者相关联;您得到的只是一个列表。

因此,如您所见,您当然拥有在命令行上传递的所有信息,但另一方面,看起来使用它并不容易。对于仅接受单个参数且没有标志的简单程序,您可以简单地使用 sys.argv[1] 来访问该参数。这没什么可耻的;我一直在这样做。对于更复杂的程序,您需要 getopt 模块。

示例 10.22. 介绍 getopt


def main(argv):                         
    grammar = "kant.xml"                 1
    try:                                
        opts, args = getopt.getopt(argv, "hg:d", ["help", "grammar="]) 2
    except getopt.GetoptError:           3
        usage()                          4
        sys.exit(2)                     

...

if __name__ == "__main__":
    main(sys.argv[1:])
1 首先,查看示例的底部,并注意您正在使用 sys.argv[1:] 调用 main 函数。请记住,sys.argv[0] 是您正在运行的脚本的名称;您在命令行处理中并不关心它,因此您将其切掉并传递列表的其余部分。
2 这就是所有有趣处理发生的地方。getopt 模块的 getopt 函数采用三个参数:参数列表(您从 sys.argv[1:] 获取)、包含此程序接受的所有可能的单字符命令行标志的字符串,以及与单字符版本等效的长命令行标志列表。乍一看,这非常令人困惑,下面将进行更详细的说明。
3 如果在尝试解析这些命令行标志时出现任何错误,getopt 将引发异常,您将捕获该异常。您已将您理解的所有标志告知 getopt,因此这可能意味着最终用户传递了您不理解的某些命令行标志。
4 按照 UNIX 世界中的标准做法,当脚本传递了它不理解的标志时,您将打印出正确用法的摘要并正常退出。请注意,我在这里没有显示 usage 函数。您仍然需要在某个地方编写代码并让它打印出适当的摘要;它不是自动的。

那么,您传递给 getopt 函数的所有这些参数是什么?第一个参数只是命令行标志和参数的原始列表(不包括第一个元素,即脚本名称,您在调用 main 函数之前已经将其切掉)。第二个参数是脚本接受的短命令行标志列表。

“hg:d”

-h
打印使用摘要
-g ...
使用指定的语法文件或 URL
-d
在解析时显示调试信息

第一个和第三个标志只是独立的标志;您可以指定它们,也可以不指定它们,它们会执行操作(打印帮助)或更改状态(打开调试)。但是,第二个标志 (-g) 必须 后跟一个参数,该参数是要从中读取的语法文件的名称。实际上,它可以是文件名或 Web 地址,您还不知道是哪个(稍后您会弄清楚),但您知道它必须是 某个东西。因此,您可以通过在传递给 getopt 函数的第二个参数中的 g 后面放置一个冒号来告诉 getopt

更复杂的是,该脚本接受短标志(如 -h)或长标志(如 --help),并且您希望它们执行相同的操作。这就是 getopt 的第三个参数的作用,用于指定与您在第二个参数中指定的短标志相对应的长标志列表。

["help", "grammar="]

--help
打印使用摘要
--grammar ...
使用指定的语法文件或 URL

这里需要注意三点

  1. 所有长标志在命令行上都以两个破折号开头,但在调用 getopt 时,您无需包含这些破折号。它们是默认的。
  2. --grammar 标志必须始终后跟一个附加参数,就像 -g 标志一样。这是用等号 "grammar=" 表示的。
  3. 长标志列表比短标志列表短,因为 -d 标志没有对应的长版本。这很好;只有 -d 会打开调试。但是短标志和长标志的顺序需要相同,因此您需要先指定所有 确实 具有对应长标志的短标志,然后再指定所有其他短标志。

困惑了吗?让我们看一下实际代码,看看它在上下文中是否有意义。

示例 10.23. 在 kgp.py 中处理命令行参数


def main(argv):                          1
    grammar = "kant.xml"                
    try:                                
        opts, args = getopt.getopt(argv, "hg:d", ["help", "grammar="])
    except getopt.GetoptError:          
        usage()                         
        sys.exit(2)                     
    for opt, arg in opts:                2
        if opt in ("-h", "--help"):      3
            usage()                     
            sys.exit()                  
        elif opt == '-d':                4
            global _debug               
            _debug = 1                  
        elif opt in ("-g", "--grammar"): 5
            grammar = arg               

    source = "".join(args)               6

    k = KantGenerator(grammar, source)
    print k.output()
1 grammar 变量将跟踪您正在使用的语法文件。您在此处对其进行初始化,以防未在命令行上指定它(使用 -g--grammar 标志)。
2 您从 getopt 获取的 opts 变量包含一个元组列表:flagargument。如果标志不带参数,则 arg 将只是 None。这使得循环遍历标志变得更容易。
3 getopt 会验证命令行标志是否可以接受,但它不会在短标志和长标志之间进行任何类型的转换。如果您指定 -h 标志,则 opt 将包含 "-h";如果您指定 --help 标志,则 opt 将包含 "--help"。因此,您需要同时检查两者。
4 请记住,-d 标志没有对应的长标志,因此您只需要检查短格式。如果找到它,则设置一个全局变量,您将在稍后引用该变量以打印出调试信息。(我在脚本开发过程中使用了它。怎么了,您以为所有这些示例都是第一次尝试就成功的?)
5 如果找到语法文件(使用 -g 标志或 --grammar 标志),则将跟随它的参数(存储在 arg 中)保存到 grammar 变量中,覆盖您在 main 函数顶部初始化的默认值。
6 就是这样。您已经循环遍历并处理了所有命令行标志。这意味着剩下的任何内容都必须是命令行参数。这些参数从 getopt 函数返回到 args 变量中。在这种情况下,您将它们视为解析器的源材料。如果没有指定命令行参数,则 args 将是一个空列表,并且 source 将最终成为空字符串。