3.1 与命令行相关的Python语言特性
3.1.1 使用sys.argv获取命令行参数
编写Linux下的命令行工具,很多时候都需要解析命令行的参数。如果参数很简单,则可以不使用解析参数的库,直接访问命令行参数。在Python中,sys库下有一个名为argv的列表,该列表保存了所有的命令行参数。argv列表中第一个元素是命令行晨星的名称,其余的命令行参数以字符串的形式保存在该列表中。
例如,现在有一个名为test_argv.py的Python文件,该文件仅仅是导入sys库,然后使用print函数打印argv列表中的内容。test_argv.py的文件内容如下:
from __future__ import print_function
import sys
print(sys.argv)
下面是一个或者命令行参数,判断文件是否存在,如果存在判断其是否可读的示例:
#!/usr/bin/python
from __future__ import print_function
import sys
import os
def main():
sys.argv.append("")
filename = sys.argv[1]
if not os.path.isfile(filename):
raise SystemExit(filename + ' does not exists')
elif not os.access(filename,os.R_OK):
raise SystemExit(filename + ' is not accessible')
else:
print(filename, ' is accessible')
if __name__ == '__main__':
main()
3.1.2 使用sys.stdin 和fileinput读取标准输入
众所周知,Shell能够通过管道把多个命令组合在一起来实现一个复杂的功能。因此,我们也希望在Python语言中使用管道来结合Python语言和Shell脚本的优势。
在Python标准库的sys库中,有三个文件描述符,分别是stdin、stdout和stderr,我们不需要调用open函数打开这几个文件就可以直接使用。例如,我们有一个名为read_stdin.py的文件,该文件仅仅是从标准输入中读取内容,然后打印到命令行终端。文件内容如下:
from __future__ import print_function
import sys
for line in sys.stdin:
print(line, end="")
接下来,我们就可以想shell脚本一样,通过标准输入给该程序输入内容,如下所示:
cat /etc/passwd |python read_stdin.py
sys.stdin是一个普通的文件对象,该对象还有一些方法,通过这些方法我们可以进一步去处理标准输入中的内容。如通过readlines函数将标准输入的内容读取到一个例表中。
如果我们可以使用fileinput来进行多文件的处理。fileinput是Python语言的一个标准库,它提供了比sys,stdin更加通用的功能。使用fileinput,可以依次读取命令行参数中给出的多个文件。也就是说,fileinput会遍历sys.argv[1:]列表,并按行一次读取列表中的文件。如果列表为空,则fileinput默认读取标准输入中的内容。示例同上:
from __future__ import print_function
import fileinput
for line in fileinput.input():
print(line, end="")
fileinput读取内容比sys.stdin更加灵活,看示例
cat /etc/passwd |python read_from_fileinput.py
python read_from_fileinput.py < /etc/passwd
python read_from_fileinput.py /etc/passwd /etc/hosts
因为fileinput可以读取多个文件的内容,所以,fileinput提供了一些方法让我们知道当前读取的内容属于哪个文件。fileinput中常用的方法有:
- filename: 当前正在读取的文件名
- fileno: 文件的描述符
- filelineno: 正在读取的行是当前文件的第几行
- isfirstline: 正在读取的行是否为当前文件的第一行
- isstdin fileinput: 正在读取文件还是直接从标准输入读取内容。
这些方法的使用也非常简单,看示例:
from __future__ import print_function
import fileinput
for line in fileinput.input():
meta = [fileinput.filename(),fileinput.fileno(),fileinput.filelineno(),
fileinput.isfirstline(),fileinput.isstdin()]
print(*meta,end="")
print(line,end="")
3.1.3 使用SystemExit异常打印错误信息
前面介绍了标准输入,如果我们要输出标准输出和错误输出的,可以使用sys.stdout.write方法和sys.stderr.write方法。如果我们的程序执行失败了,通常情况下我们会需要在标准版错误中输出错误信息,然后以非零的返回码退出程序。示例如下:
import sys
sys.stderr.write('error message')
sys.exit(1)
3.1.4 使用getpass库读取密码
getpass是一个非常简单的Python标准库,主要包含getuser函数和getpass函数。前者用来从环境变量中获取用户名,后者用来等待用户输入密码。getpass函数和input函数的区别在于,它不会将我们输入的密码显示在命令行中,从而避免我们输入的密码被他们看到。如下所示:
from __future__ import print_function
import getpass
user=getpass.getuser()
passwd=getpass.getpass('your password: ')
print(user,passwd)
3.2 使用ConfigParse解析配置文件
使用配置文件配置参数是很常见的需求,因此,各个语言都提供了相应的模块来解析配置文件。在Python语言中,标准库的ConfigParser模块用以解析配置文件。ConfigParser模块中包含了一个ConfigParser类,一个ConfigParser对象可以同时解析多个配置文件,一般情况下,我们只会使用ConfigParser解析一个配置文件。ConfigParser类提供了很多方法,我们可以使用这些方法解析、读取和修改配置文件。
要解析一个配置文件,首先要创建一个ConfigParser对象。创建ConfigParser时有多个参数,其中,比较重要的是allow_no_value。allow_no_value默认取值为False,表示在配置文件中不允许没有值的情况。但是有一些特殊环境,会有这种情况,如mysql的配置文件。
有了ConfigParser对象以后,可以使用read方法从配置文件中读取配置内容,也可以使用readfp方法从一个已经打开的文件中读取配置内容。
from __future__ import print_function
import ConfigParser
cf=ConfigParser.ConfigParser(allow_no_value=True)
cf.read('my.cnf')
ConfigParser中有很多方法,其中与读取配置文件,判断配置相关的方法有:
- sections: 返回一个包含所有章节的列表
- has_section: 判断章节是否存在
- items:以元祖的形式返回所有选项的列表
- options: 返回一个包含章节下所有选项的列表;
- has_option: 判断某个选项是否存在
- get、getboolean、getini、getfload: 判断选项的值
以上面打开的配置文件为例
In [5]: cf.sections()
Out[5]: ['client', 'mysql', 'mysqld', 'mysqldump']
In [6]: cf.has_section('client')
Out[6]: True
In [7]: cf.options('client')
Out[7]: ['port', 'socket']
In [8]: cf.has_option('client','port')
Out[8]: True
In [9]: cf.get('client','port')
Out[9]: '3306'
In [10]: cf.getint('client','port')
Out[10]: 3306
ConfigParser提供了很多方法便于我们修改配置文件。如下:
- remove_section: 删除一个章节
- add_section: 添加一个章节
- remove_option: 删除一个选项
- set: 添加一个选项
- write: 将ConfigParser对象中的数据保存到文件中。
示例略过。
3.3 使用argparse解析命令行参数
对于命令行工具来说,命令行参数比陪你文件的使用更加广泛。在Python中,agrparse是标准库中用来解析命令行参数的模块,用来替代已经过时的optparse模块。argparse能够根据程序中的定义从sys.argv中解析出这些参数,并自动生成帮助和使用信息。
3.3.1 ArgumentParse解析器
使用argparse解析命令行参数时,首先需要创建一个解析器,创建方式如下所示:
import argparse
parser=argparse.ArgumentParser()
ArgumentParse类的初始化函数有多个参数,其中比较常用的是description。description是程序的描述信息,即帮助信息前的文字。参数内容先略过,下面是一个例子。
from __future__ import print_function
import argparse
def _argparse():
parser = argparse.ArgumentParser(description="This is description")
parser.add_argument('--host', action='store',
dest='server',default="localhost", help='connect to host')
parser.add_argument('-t', action='store_true',
default=False, dest='boolean_switch', help='Set a switch to true')
return parser.parse_args()
def main():
parser = _argparse()
print(parser)
print('host =', parser.server)
print('boolean_switch=', parser.boolean_switch)
if __name__ == '__main__':
main()
由于我们为所有的选项都提供了默认值,因此,即使不传递任何参数也不会出错,如下:
[pangcm@blog_vm py_script]$ python test_argparse.py
Namespace(boolean_switch=False, server='localhost')
host = localhost
boolean_switch= False
[pangcm@blog_vm py_script]$ python test_argparse.py --host=127.0.0.1 -t
Namespace(boolean_switch=True, server='127.0.0.1')
host = 127.0.0.1
boolean_switch= True
使用argparse进行参数解析还有一个好处就是,它会自动生成帮助信息,如下:
[pangcm@blog_vm py_script]$ python test_argparse.py --help
usage: test_argparse.py [-h] [--host SERVER] [-t]
This is description
optional arguments:
-h, --help show this help message and exit
--host SERVER connect to host
-t Set a switch to true
3.3.2 模仿MySQL客户端的命令行参数
from __future__ import print_function
import argparse
def _argparse():
parser = argparse.ArgumentParser(description='A Python-MySQL client')
parser.add_argument('--host', action='store', dest='host',
required=True, help='connect to host')
parser.add_argument('-u', '--user', action='store', dest='user',
required=True, help='user for login')
parser.add_argument('-p', '--password', action='store',
dest='password',required=True, help='password to use when connecting to server')
parser.add_argument('-P', '--port', action='store', dest='port',
default=3306, type=int, help='port number to use for connection or 3306 for default')
parser.add_argument('-v', '--version', action='version', version='%(prog)s 0.1')
return parser.parse_args()
def main():
parser = _argparse()
conn_args = dict(host=parser.host, user=parser.user,
password=parser.password, port=parser.port)
print(conn_args)
if __name__ == '__main__':
main()
3.4 使用loggin记录日志
对比自己写print函数打印程序的中间结果,使用标准库的日志模块有很多好处,包括:
- 所有日志具有统一的格式,便于后续处理
- 丰富的日志格式,只需要通过配置文件就可以修改日志的格式,不需要修改代码
- 根据重要性对日志进行分类,可以只显示重要的日志
- 自动管理日志文件,如按天切换一个新的文件,只保留一个月的日志文件等。
3.4.1 日志的作用
重要到不用说吧,比如诊断日志,排查问题;审计日志,为商业行为分析日志,如pv。
3.4.2 Python的logging模块
在最简单的使用中,我们直接导入logging模块,然后调用它的debug、info、warn、error、critical等函数记录日志。默认情况下,logging模块将日志打印到屏幕终端,日志级别为WARNING,也就是说,只有日志级别比WARNING高的日志才会被显示,如下所示:
#!/usr/bin/python
import logging
logging.debug('debug message')
logging.info('info message')
logging.warn('warn message')
logging.error('error message')
logging.critical('critical message')
日志的级别是一个逻辑上的概念,用来区分日志的重要程度。将日志分为不同的级别后,一方面可以在大多数时间只保存级别比较高的日志来提供性能;另一方面也便于日志的分析。
在Python的logging模块中,分为5分级别,其含义为:
日志级别 | 权重 | 含义 |
---|---|---|
CRITICAL | 50 | 严重错误,表明程序已经不能继续运行了 |
ERROR | 40 | 发生了严重错误,必须马上处理 |
WARNING | 30 | 应用程序可以容忍这些信息,软件可以正常运行,不过他们应该被检查及修复,否则将在不久的将来发生问题 |
INFO | 20 | 证明事情按预期工作,突出强调应用程序的运行过程 |
DEBUG | 10 | 详细信息,只有开发人员调试程序时才需要关注的事情 |
3.4.3 配置日志格式
在使用logging记录日志之前,我们可以进行一些简单的配置,如下:
#!/usr/bin/python
import logging
logging.basicConfig(filename='app.log',level=logging.INFO)
logging.debug('debug message')
logging.info('info message')
logging.warn('warn message')
logging.error('error message')
logging.critical('critical message')
执行上面的程序,会在当前目录下产生一个app.log文件。该文件存在INFO及INFO以上级别的日志。
尅看到,我们可以通过basicConfig方法对日志进行简单的配置,我们也可以进行更加复杂的日志配置。在这之前,需要先了解logging模块中的几个概念,即Logger、Handler及Formatter。
- Logger: 日志记录器,是应用程序中能直接使用的接口
- Handler: 日志处理器,用以表名将日志保存到什么地方以及保存多久
- Formatter: 格式化,用以配置日志输出的格式。
对于比较简单的脚本,可以直接使用basicConfig在代码中配置日志。对于比较复杂的项目,可以将日志的配置保存到一个配置文件中,然后再代码中使用fileConfig函数读取配置文件。
下面是一个Python源码中配置日志的例子。
#!/usr/bin/python
import logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s : %(levelname)s : %(message)s',
filename='app.log')
logging.debug('debug message')
...
logging.critical('critical message')
对于复杂的项目,一般将日志配置保存在配置文件中,如下:
[loggers]
keys = root
[handlers]
keys = logfile
[formatters]
keys = generic
[logger_root]
handlers = logfile
[handler_logfile]
class = handlers.TimedRotatingFileHandler
args = ('app.log', 'midnight', 1, 10)
level = DEBUG
formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s] %(message)s
在这个日志配置文件中,我们首先在[loggers]中声明一个名为root的logger,在[handlers]中声明一个名为logfile的handler,并在[formatters]中声明一个名为generic的formatter。然后,我们在[logger_root]中定义root这个logger所使用的handler,在[handler_logfile]中定义了handler输出日志的方式、日志文件的切换时间等。最后,在[formatter_generic]中定义了日志的格式,包括日志产生的时间、日志的级别、产生日志的文件名和行号等信息。
有了配置文件以后,在Python代码中使用logging.config模块的fileConfig函数加载日志配置,如下所示:
import logging
import logging.config
logging.config.fileConfig('logging.cnf')
logging.debug('debug message')
...
logging.critical('critical message')
3.5 与命令行相关的开源项目
3.5.1 使用click解析命令行参数
Click是Flask的作者开发的一个第三方模块,用于快速创建命令行。它的作用与Python标准库的argprese相同,但是,使用更加简单。Click相对于标准库的argparse,就好比requests相对于标准库的urllib.
click是一个第三方库,首先要先安装才能使用。
pip install click
Click对argparse的主要改进在易用性,使用Click分为两个步骤:
- 使用@click.command()装饰一个函数,使之成为命令行接口;
- 使用@click.option()等装饰函数,为其添加命令行选项等。
示例如下:
import click
@click.command()
@click.option('--count', default=1, help='Number of greetings.')
@click.option('--name', prompt='Your name',help='The person to greet.')
def hello(count, name):
"""Simple program that greets NAME for a total of COUNT times."""
for x in range(count):
click.echo('Hello %s!' % name)
if __name__ == '__main__':
hello()
在上面的例子中,函数hello接受两个参数,分别是count和name,它们的取值从命令行中获取。在这段程序中,我们使用了click模块中的command、option和echo,他们的作用如下:
- command:使函数hello成为命令行接口
- option: 增加命令行选项
- echo: 输出结果,使用echo进行输出是为了获得更好的兼容性,因为Python2和Python3的print选项不是同一个东西来的。
运行上面的程序,可以通过命令行指定count和name的取值。由于我们在option函数中使用了prompt选项,因此,当我们没有直接指定name这个参数的时候,Click会提示我们在交互模式下输入,如下所示:
python hello.py --count=3
至于Click如何实现这些功能的,略过。
3.5.2 使用 prompt_toolkit 打造交互式命令行工具
如果你要打造一个用户体验良好的交互式命令行程序,那么可以了解prompt_toolkit的特性。使用prompt_toolkit能够支持语法高亮、支持代码补全可以使用Vi风格的快捷键等特性。我这里略过,有需要的百度去了解。