docopt:构造一个漂亮的命令行工具

   2016-10-01 0
核心提示:最近拜读了覃超在知乎专栏的文章 《谁说程序员不是潜力股?!让这位世界前五名的天才程序员来颠覆你的三观!》 ,受到了深深的一击。于是跪着爬进了Kenneth的 Github ,汲取点营养。我先找了一个单份文件的小工具 pip-pop 看起(潜台词:没敢一开始就从request

最近拜读了覃超在知乎专栏的文章 《谁说程序员不是潜力股?!让这位世界前五名的天才程序员来颠覆你的三观!》 ,受到了深深的一击。于是跪着爬进了Kenneth的 Github ,汲取点营养。

我先找了一个单份文件的小工具 pip-pop 看起(潜台词:没敢一开始就从requests干起,怕齁着了==)。这是一个用于分析requirements.txt文件的程序,挺简单的。其中引起我注意的是它用到的docopt。

docopt 是一个命令行接口描述语言,用于定义命令行程序的各项参数,并且生成一个处理分析参数的分析器。

说明

先看一份官方文档中的例子:

"""Naval Fate.

Usage:
naval_fate.py ship new <name>...
naval_fate.py ship <name> move <x> <y> [--speed=<kn>]
naval_fate.py ship shoot <x> <y>
naval_fate.py mine (set|remove) <x> <y> [--moored | --drifting]
naval_fate.py (-h | --help)
naval_fate.py --version

Options:
-h --help Show this screen.
--version Show version.
--speed=<kn> Speed in knots [default: 10].
--moored Moored (anchored) mine.
--drifting Drifting mine.

"""

from docopt import docopt


if __name__ == '__main__':
arguments = docopt(__doc__, version='Naval Fate 2.0')
print(arguments)

首先,在Usage下定义了这个命令行工具(naval_fate.py)的6个使用模式,真正调用时一定要匹配到这6个的其中之一。

在每个模式中,<>包围的是位置参数,[]包围的是可选参数,()包围的是必选参数,|用于分割两个互斥的参数。省略号…用于表示格式为数组的参数。

其次,在Options下面是对参数的描述。参数与其描述之间用两个以上的空格分隔,如果参数有默认值的话,则在描述字符串之后,用[default: val]注明。

最后,使用docopt(__doc__, version=’Naval Fate 2.0’)生成传入参数组成的dict,dict的key就是上面描述中的参数字符串。

那么,arguments可能的形式如下:

{'--drifting': False,
'--help': False,
'--moored': False,
'--speed': '10',
'--version': False,
'<name>': ['abc'],
'<x>': '0',
'<y>': '0',
'mine': False,
'move': True,
'new': False,
'remove': False,
'set': False,
'ship': True,
'shoot': False}

实践

我们用一个小程序实践一下。

在我的工作中,有这样一个目录:

- code
- server
- client
- data
- common
- cdata
- common_server
-data

其中server、client、common、common_server下面都有python代码文件,而data、cdata目录下都是python数据文件。python数据文件由csv文件转化而来,数据量庞大。当我在code目录下需要grep某一个关键字时,往往会搜索所有的数据文件,耗时严重,其实我只想要在程序文件中搜索。

一般的,我们可以这样组合find命令和grep命令,实现这一功能:

find . \( -wholename ./client/data -prune \) -o \( -wholename ./common/cdata -prune \) -o \( -wholename ./common_server/data -prune \) -o -name "*.py" -print | xargs grep -n --color sth_you_want_to_grep

我们也可以写一个python程序,结合正则表达式re模块,实现这一功能。

分析我们这个小程序:

  1. 有两个参数必不可少,即搜索的根目录,以及所搜索的字符串(或者正则表达式)
  2. 一个可选参数,用于表明搜索过程中,强制跳过的目录序列
  3. 一个可选参数,用于表示是否忽略大小写

那么,设计出来的docopt描述字符串如下:

"""Usage:
py-grep <searchpath> <pattern> [-i] [--ignorepath <igpaths>...]
py-grep (-h | --help)
Options:
-h --help Show this screen
-i Ignore case
--ignore_paths Ignored directories
"""

接下来,根据此字符串可以解析传递给py-grep程序的各项参数:

def main():
args = docopt(__doc__, version="py-grep")

if args['-i']:
pattern = re.compile(args['<pattern>'], re.IGNORECASE)
else:
pattern = re.compile(args['<pattern>'])

kwargs = {
'search_path': args['<searchpath>'],
'pattern': pattern,
'ignore_paths': args['<igpaths>'],
}
py_grep(**kwargs)
return

在py_grep函数中,我们将利用os.walk遍历目录下的文件,跳过ignore_paths,使用pattern对文件的每一行进行搜索。

def py_grep(search_path, pattern, ignore_paths=None):
ignore_paths = ignore_paths if ignore_paths else []
ignore_paths = [os.path.abspath(p) for p in ignore_paths]
for parent, dirnames, filenames in os.walk(search_path):
abs_parent = os.path.abspath(parent)
is_ignore = False
for ig_path in ignore_paths:
if abs_parent.startswith(ig_path):
is_ignore = True
break
if is_ignore:
continue
for fn in filenames:
fn = os.path.join(parent, fn)
with open(fn, 'r') as fobj:
for n, line in enumerate(fobj):
if pattern.search(line):
print fn, n+1, ':', line.strip()
return

在如下目录结构中尝试一下:

|-- example.sh
|-- py-grep
|-- test_file1
|-- test_path1
| |-- inner_path
| | `-- test_file4
| `-- test_file2
`-- test_path2
`-- test_file3

输出如下:

./py-grep . "(\w)+@(\w)+((\.\w+)+)" --ignorepath ./test_path1
./test_file1 2 : My email is yubo1911@163.com.
./test_file1 4 : I have another email: usher@gmail.com
./test_path2/test_file3 2 : My email is yubo1911@163.com.
./test_path2/test_file3 4 : I have another email: usher@gmail.com
====================
./py-grep . "(\w)+@(\w)+((\.\w+)+)" --ignorepath test_path1
./test_file1 2 : My email is yubo1911@163.com.
./test_file1 4 : I have another email: usher@gmail.com
./test_path2/test_file3 2 : My email is yubo1911@163.com.
./test_path2/test_file3 4 : I have another email: usher@gmail.com

输出结果符合预期。

总结

docopt的简介就到这里了。更详细的信息,请参阅其 官方文档

PS. 这里写的py-grep一定是有性能问题的,只用于熟悉docopt的用法,请不要将其用于日常工作中。

完整代码详见 docopt

转载请注明出处: http://blog.guoyb.com/2016/09/26/docopt/

欢迎使用微信扫描下方二维码,关注我的微信公众号TechTalking,技术·生活·思考:

docopt:构造一个漂亮的命令行工具
 
标签: Linux命令 Python
反对 0举报 0 评论 0
 

免责声明:本文仅代表作者个人观点,与乐学笔记(本网)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
    本网站有部分内容均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,若因作品内容、知识产权、版权和其他问题,请及时提供相关证明等材料并与我们留言联系,本网站将在规定时间内给予删除等相关处理.

  • 每天一条linux命令——halt
    halt命令用来关闭正在运行的Linux操作系统。halt命令会先检测系统的runlevel,若runlevel为0或6,则关闭系统,否则即调用shutdown来关闭系统。 语法:halt(选项)选项:-d:不要在wtmp中记录;-f:不论目前的runlevel为何,不调用shutdown即强制关闭系统;-i:
    02-10
  • 几个linux命令查看堆栈内存问题 几个linux命令
      公司编写代码,发现整个在程序里面调用system时候总是失败,原因是can't allocate memory ,根本原因不是内存不足,而是堆栈内存不够。使用ulimit -s 2048后有所好转但是发现一旦系统进程开启多了后仍然会挂。后来使用objdump -x 后发现了原因有个动态库
    02-09
  • Linux命令总结--rm命令 linux的rmp命令
    (来源于http://www.cnblogs.com/xqzt/p/5398919.html)1、命令简介rm(Remove file 删除目录或文件)删除文件,对于链接文件,只是删除整个链接文件,而原有文件保持不变。新手在删除文件之前一定要知道这些:很重要1.可以用mv代替rm的绝对不要用rm,不要着急
    02-09
  • Linux命令行批量创建目录详解 linux创建目录
    以前一直用-p创建目录链,觉得很方便了。在空目录/opt/app/myapp里创建src,再创建main,再创建javamkdir -p /opt/app/myapp/src/main/java没想到还可以这样玩##¥%……*(root@vm1:~/tmp# mkdir -p src/{{main,test}/{java,resources},main/webapp}root@vm1:
    02-09
  • Linux命令之乐--script和scriptplay
    script和scriptplay可以把终端会话记录到一个文件中,可以用来制作命令行教学视屏。 开始录制会话[root@new test]# script -t 2timing.log -a output.sessionScript started, file is output.session[root@new test]# echo hellohello[root@new test]# echo
    02-09
  • java 开发常用的Linux命令
    1.查找文件 find / -name filename.txt 根据名称查找/目录下的filename.txt文件。find . -name "*.xml" 递归查找所有的xml文件find . -name "*.xml" |xargs grep "hello world" 递归查找所有文件内容中包含hello world的xml文件grep -H 'spring' *.xml 查找所
    02-09
  • 常用Linux命令整理 Linux常用命令总结
    常用Linux命令整理常见系统命令export 查看或修改环境变量# 例:临时修改命令提示符为字符串$export PS1=$# 例:临时修改命令提示符显示系统时间 时间使用\t 表示export PS1="[\u@\h \t \W]\$"man 查看linux系统的手册# 例:查看ls命令如何使用man ls # 回车
    02-09 Linux
  • linux命令行—《命令行快速入门》 linux常用命令csdn
    linux命令行—《命令行快速入门》 linux常用命
    pwd print working directory 打印工作目录hostname my computer's network name 电脑在网络中的名称mkdir make directory 创建路径cd change directory 改变路径ls list directory 列出路径下的内容rmdir remove directory 删除路径pushd push directory 推
    02-09
  • Linux命令(五) 五个常用的linux命令
    Linux命令(五) 五个常用的linux命令
    jenkins触发钉钉报警机制1、安装钉钉插件钉钉报警需要安装Ding Talk插件,支持钉钉报警2、打开钉钉创建钉钉机器人2.1选择机器人类型——自定义2.2添加机器人2.3填写机器人信息填写机器人姓名—设置安全设置为加密类型,并复制出来加密值2.4创建完成保持webhoo
    02-09
  • 03_Linux基础-文件类型-主辅提示符-第1提示符-Linux命令-内外部命令-快捷键-改为英文编码-3个时间-stat-其他基础命令
    03_Linux基础-文件类型-主辅提示符-第1提示符-L
    03_Linux基础-文件类型-主辅提示符-第1提示符-Linux命令-内外部命令-快捷键-改为英文编码-3个时间-stat-{1..100}-du-cd-cp-file-mv-echo-id-shell-ln-env-set-which-rpm-/usr-/与/root-hostname-vim-cat-pwd-alias-unalias-ls-ASCII-wc-chmod博客
    02-09
点击排行