{ "cells": [ { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Python 开发命令行工具\n", "\n", "Python 作为一种脚本语言,可以非常方便地用于系统(尤其是\\*nix系统)命令行工具的开发。Python 自身也集成了一些标准库,专门用于处理命令行相关的问题。" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "#### 命令行工具的一般结构\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true, "slideshow": { "slide_type": "slide" } }, "source": [ "**1. 标准输入输出**\n", "\n", "\\*nix 系统中,一切皆为文件,因此标准输入、输出可以完全可以看做是对文件的操作。标准化输入可以通过管道(pipe)或重定向(redirect)的方式传递:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": true, "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "# script reverse.py\n", "#!/usr/bin/env python\n", "import sys\n", "for l in sys.stdin.readlines():\n", " sys.stdout.write(l[::-1])" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "保存为 `reverse.py`,通过管道 `|` 传递:\n", "\n", "```sh\n", "chmod +x reverse.py\n", "cat reverse.py | ./reverse.py\n", "\n", "nohtyp vne/nib/rsu/!#\n", "sys tropmi\n", ":)(senildaer.nidts.sys ni l rof\n", ")]1-::[l(etirw.tuodts.sys\n", "```\n", "\n", "通过重定向 `<` 传递:\n", "\n", "```sh\n", "./reverse.py < reverse.py\n", "# 输出结果同上\n", "```" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "**2. 命令行参数**\n", "\n", "一般在命令行后追加的参数可以通过 `sys.argv` 获取, `sys.argv` 是一个列表,其中第一个元素为当前脚本的文件名:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false, "slideshow": { "slide_type": "slide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['/Users/rainy/Projects/GitHub/pytips/venv3/lib/python3.5/site-packages/ipykernel/__main__.py', '-f', '/Users/rainy/Library/Jupyter/runtime/kernel-0533e681-bd7c-4c4d-9094-a78fde7fc2ed.json']\n" ] } ], "source": [ "# script argv.py\n", "#!/usr/bin/env python\n", "import sys\n", "print(sys.argv) # 下面返回的是 Jupyter 运行的结果" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "运行上面的脚本:\n", "\n", "```sh\n", "chmod +x argv.py\n", "./argv.py hello world\n", "python argv.py hello world\n", "\n", "# 返回的结果是相同的\n", "# ['./test.py', 'hello', 'world']\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "对于比较复杂的命令行参数,例如通过 `--option` 传递的选项参数,如果是对 `sys.argv` 逐项进行解析会很麻烦,Python 提供标准库 [`argparse`](https://docs.python.org/3/library/argparse.html)(旧的库为 `optparse`,已经停止维护)专门解析命令行参数:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Load config from: config.ini\n", "Set theme: default.theme\n" ] } ], "source": [ "# script convert.py\n", "#!/usr/bin/env python\n", "import argparse as apa\n", "def loadConfig(config):\n", " print(\"Load config from: {}\".format(config))\n", "def setTheme(theme):\n", " print(\"Set theme: {}\".format(theme))\n", "def main():\n", " parser = apa.ArgumentParser(prog=\"convert\") # 设定命令信息,用于输出帮助信息\n", " parser.add_argument(\"-c\", \"--config\", required=False, default=\"config.ini\")\n", " parser.add_argument(\"-t\", \"--theme\", required=False, default=\"default.theme\")\n", " parser.add_argument(\"-f\") # Accept Jupyter runtime option\n", " args = parser.parse_args()\n", " loadConfig(args.config)\n", " setTheme(args.theme)\n", "\n", "if __name__ == \"__main__\":\n", " main()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "利用 `argparse` 可以很方便地解析选项参数,同时可以定义指定参数的相关属性(是否必须、默认值等),同时还可以自动生成帮助文档。执行上面的脚本:\n", "\n", "```sh\n", "./convert.py -h\n", "usage: convert [-h] [-c CONFIG] [-t THEME]\n", "\n", "optional arguments:\n", " -h, --help show this help message and exit\n", " -c CONFIG, --config CONFIG\n", " -t THEME, --theme THEME\n", "```" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "**3. 执行系统命令**\n", "\n", "当 Python 能够准确地解读输入信息或参数之后,就可以通过 Python 去做任何事情了。这里主要介绍通过 Python 调用系统命令,也就是替代 `Shell` 脚本完成系统管理的功能。我以前的习惯是将命令行指令通过 `os.system(command)` 执行,但是更好的做法应该是用 [`subprocess`](https://docs.python.org/3.5/library/subprocess.html) 标准库,它的存在就是为了替代旧的 `os.system; os.spawn*` 。" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "`subprocess` 模块提供简便的直接调用系统指令的`call()`方法,以及较为复杂可以让用户更加深入地与系统命令进行交互的`Popen`对象。" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false, "slideshow": { "slide_type": "slide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "-rw-r--r-- 1 rainy staff 3.4K 3 8 17:36 ./2016-03-06-The-Zen-of-Python.ipynb\n", "-rw-r--r-- 1 rainy staff 6.7K 3 8 17:45 ./2016-03-07-iterator-and-generator.ipynb\n", "-rw-r--r-- 1 rainy staff 6.0K 3 10 12:35 ./2016-03-08-Functional-Programming-in-Python.ipynb\n", "-rw-r--r-- 1 rainy staff 5.9K 3 9 16:28 ./2016-03-09-List-Comprehension.ipynb\n", "-rw-r--r-- 1 rainy staff 10K 3 10 14:14 ./2016-03-10-Scope-and-Closure.ipynb\n", "-rw-r--r-- 1 rainy staff 8.0K 3 11 16:30 ./2016-03-11-Arguments-and-Unpacking.ipynb\n", "-rw-r--r-- 1 rainy staff 8.5K 3 14 19:31 ./2016-03-14-Command-Line-tools-in-Python.ipynb\n", "\n" ] } ], "source": [ "# script list_files.py\n", "#!/usr/bin/env python\n", "import subprocess as sb\n", "res = sb.check_output(\"ls -lh ./*.ipynb\", shell=True) # 为了安全起见,默认不通过系统 Shell 执行,因此需要设定 shell=True\n", "print(res.decode()) # 默认返回值为 bytes 类型,需要进行解码操作" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "如果只是简单地执行系统命令还不能满足你的需求,可以使用 `subprocess.Popen` 与生成的子进程进行更多交互:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false, "slideshow": { "slide_type": "slide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " \" \\\"p = sb.Popen(['grep', 'communicate'], stdout=sb.PIPE)\\\\n\\\",\\n\",\n", " \" \\\"# res = p.communicate(sb.check_output('cat ./*'))\\\"\\n\",\n", " \"p = sb.Popen(['grep', 'communicate'], stdin=sb.PIPE, stdout=sb.PIPE)\\n\",\n", " \"res, err = p.communicate(sb.check_output('cat ./*', shell=True))\\n\",\n", "\n" ] } ], "source": [ "import subprocess as sb\n", "\n", "p = sb.Popen(['grep', 'communicate'], stdin=sb.PIPE, stdout=sb.PIPE)\n", "res, err = p.communicate(sb.check_output('cat ./*', shell=True))\n", "if not err:\n", " print(res.decode())" ] } ], "metadata": { "celltoolbar": "Slideshow", "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.5.0" } }, "nbformat": 4, "nbformat_minor": 0 }