使用 Python 编写脚本并发布

P1: 脚本

通常在 Linux 服务器上会遇到在命令行中输入命令的操作,而有些操作包含的命令数目较多或者其中的命令包含的参数较多,如果一个一个的敲命令的话就太麻烦了,有几种做法可以简化操作:

  1. 使用 alias 为命令编写别名,比如我之前开发一个网站程序 minor-sspymgr 时,经常需要上传修改后的代码,更新服务器上的代码,重启网站程序。为了方便,我定义一个 alias 别名命令:
alias updateMgr='cd ~/minor-sspymgr/ && git pull origin master && pm2 restart sspymgr'

这种方法的优点是很方便简单,把常用的命令组合到一起,用一个别名表示即可使用。当然,它的缺点也很明显,就是不能执行一些复杂的命令,而且难以将参数应用到命令中。

  1. 编写一个 .sh 脚本,我之前写简单脚本时用的都是 bash,对于上述例子,用 sh 脚本编写起来也很类似,可以简单的把上述命令以 && 拆分为 3 行:
#!/bin/bash

cd ~/minor-sspymgr/
git pull origin master
pm2 restart sspymgr

当然了,实际的脚本远非如此简单,可以用 $1 ... $n 获取命令行参数,执行一些更复杂的逻辑。

为什么用 Python 写脚本

既然用 bash 已经可以编写一些脚本了,那么为什么我还要用 Python 编写脚本呢?原因有两个:1. bash 用的不多,学的也早,而且 bash 和常用的编程语言的语法有些差别,很多内容都容易忘记,用 Python 写的话也可以熟悉 Python(尽管我是从 flask 制作一个 WSGIServer 开始使用 Python 的),2. Python 的库很多也很方便使用,编写脚本给人一种流畅的感觉。当然最主要的原因就是因为 Python 里面有 argparse 这个库,对于解析命令行参数来说十分方便。


如何解析命令行参数

bash 脚本里面获取命令行参数的方式很简单,但是解析起来却比较麻烦,如果有 bash 解析命令行参数的库,希望可以推荐给我。不过即使有这样的库,估计我还是会选择 Python 来编写脚本了。在 Python 里解析命令行参数的模块比较多,有 getoptoptparseargparse1。目前我只用过 argparse,因此这里也只会涉及到如何用 argparse 来解析命令行参数。至于前两个模块,如果有兴趣的话,可以自行了解。argparse 使用起来非常简单:

from argparse  import ArgumentParser
parser = ArgumentParser(description="""Run this script under SUPER USER privilege. Create or remove git repository
under direcotry: {}. Currently only works for Linux. Most operations are only tested under Linux.
Script version : {}.
""".format(config["repo_dir"], __version__))
parser.add_argument("-V", "--version", help="Print this script version", action="store_true")
args = parser.parse_args()
if args.version:
print("Script version is {}".format(__version__))

上面这几行代码就做好了一个简单的命令行解析功能,当我们输入 script.py -V 的时候就会打印出脚本的版本。不需要在意命令行参数的位置或者是否必须要加上这个参数,只需要将注意力放在代码的逻辑上即可,argparse 这个模块可以极大的方便我们解析命令行参数。


P2: 示例:初始化 git 仓库的脚本

之前用 git 搭建过源代码管理服务器 23。如果要在这样的服务器上添加一个共享仓库,就需要在仓库根目录下面执行一些操作了。比如说,我的 git 仓库存放的目录是 /src 这个目录下面的所有文件及文件夹的属主和属组都是 git,要想新建一个共享仓库,最开始我的做法是:

  1. cd /src
  2. mkdir newrepo && cd newrepo
  3. git init --bare --shared
  4. cd ..
  5. sudo chown -R git:git newrepo && sudo chmod -R g+rwx newrepo

步骤还是有点多的,每次都要敲这么多命令很麻烦,把这些命令写到 bash 脚本中,比如叫做 gitrepo.sh,给它加上 x 执行权限,在终端里输入 sudo gitrepo.sh reponame 就能完成上面的操作。

用 Bash 编写的脚本

Bash 脚本,编写简单,就是把在命令行中敲过的命令依次写到文件中即可,但是对于不常写 bash 脚本的我来说,要编写一个完备的脚本,并且要包含参数、异常处理等逻辑来说比较麻烦:

#!/bin/bash

# filename: gitrepo.sh
# @author BriFuture
# @details create bare and shared repository within /src directory repoName=$1 if [ -z "$repoName" ]; then
echo "Repo is empty!"
exit 1
fi dotpos=`expr index "$repoName" "."` if [ "$dotpos" -gt 0 ]; then
echo "found";
else
repoName=$repoName".git"
fi # go to the repository dir
cd /src mkdir $repoName cd $repoName echo "Init git repo in $repoName" git init --bare --shared # change the own of repository
cd ../ sudo chown -R git:git $repoName
sudo chmod -R g+rwx $repoName

用 Python 编写的脚本

实际上,我用 Python 重新编写这个脚本时,在参数中加入了更多的可选项,此前的 bash 脚本只能够添加仓库,这个全新的脚本加入了其他操作:添加新的仓库,列出所有仓库,删除一个仓库。并且完善了命令行的帮助信息,为脚本添加了配置文件以及输出日志。

另外在执行脚本的过程中需要用到超级用户的权限,因此在脚本中添加了检查当前用户权限的功能:

def is_root():
if hasattr(os, "getuid"):
return os.getuid() == 0
return False

该 Python 脚本的主要功能是添加和删除仓库(目录),添加仓库的操作如下,分别是创建目录,执行 git init --bare --shared 命令,更改文件属主和属组的操作(实际的脚本中包含一些日志记录和异常处理的代码):

from pathlib import Path
def createRepo(repo: Path):
if repo.exists():
return
repo.mkdir(parents=True)
subprocess.run(["git", "init", "--bare", "--shared", str(repo.absolute())])
shutil.chown(repo, config["user"], config["group"])

删除仓库的操作更加简单,调用 shutil 递归删除文件夹即可,不能使用 repo.rmdir() 函数,因为一般仓库文件夹是非空的:

import shutil
def deleteRepo(repo: Path):
if not repo.exists():
return
shutil.rmtree(str(repo))

罗列所有仓库使用 Path.iterdir() 函数即可。

这样一个可以使用的脚本就制作完成了,如果只是单纯的编写一个脚本的话,可以在文件的开头加上 #!/usr/bin/python3 这样的标记,表明这是一个可执行的 Python3 脚本,在 linux 系统上给它加上 x 权限,比如这个文件的名称为 gitrepo.py,我们可以在终端中输入 gitrepo.py -h 或者 python3 gitrepo.py -h 查看这个命令的帮助信息。

详细的代码可以在 Github 上的 gitrepo.py 文件中查看。


P3: 以 wheel 包的形式发布

前面说过,我们可以把写好的 Python 文件当做脚本执行,但是每次都要敲 gitrepo.py -h 这样的命令,能不能就像平时用 ls 这些命令一样直接敲 gitrepo -h 呢?最简单的是用 ln 建立软链接:sudo ln -s /path/to/gitrepo.py /usr/bin/gitrepo,但是这里我们可以借助 pypi 发布我们已经写好的命令,将我们的脚本发布到 pypi 上,那么还可以在没有该脚本的机器上利用 pip 进行安装。

brifuture-facilities 这个项目为例,在项目的根目录下面新建一个 setup.py 文件,输入以下内容:

# -*- coding: utf-8 -*-

from setuptools import setup, find_packages

with open("README.md", "r", encoding="utf-8") as fh:
long_description = fh.read() with open('requirements.txt', "r", encoding="utf-8") as f:
requires = f.read().splitlines() from myfacilities import __version__ setup(
name = "brifuture-facilities",
packages = find_packages(where='.'),
version = __version__, entry_points = {
"console_scripts": [
'bf_broadcast = myfacilities.broadcast:main',
'bf_gitrepo = myfacilities.gitrepo:main',
]
}, description = "BriFuture's scripts set, all scripts will be written with Python3",
author = "BriFuture",
author_email = "jw.brifuture@gmail.com",
license = "GPLv3",
url = "http://github.com/brifuture/", install_requires = requires, include_package_data = True,
zip_safe=True,
exclude_package_data = {'': ['__pycache__']}, # download_url = "",
keywords = [ "webserver", "socks-manager" ],
classifiers = [
"Programming Language :: Python",
"Programming Language :: Python :: 3" ,
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Operating System :: OS Independent"
], long_description = long_description,
long_description_content_type="text/markdown",
)

我们需要利用的是 setuptools,从这个模块中导入 setup,它的参数都很直观,我们想要添加一个可执行的脚本时,在 entry_points 关键字参数中添加即可,如:

entry_points = {
"console_scripts": [
'bf_broadcast = myfacilities.broadcast:main',
'bf_gitrepo = myfacilities.gitrepo:main',
]
},

由于这个项目中的文件是包的一部分,所以我没法直接使用 python 运行其中的某个脚本,像 python3 myfacilities.gitrepo -h 是无法运行的,但是借助于 setuptools,我们可以将写好的程序安装到机器中,python3 setup.py install 可以通过 setup.py 文件进行安装,也可以使用 pip install . 进行安装,详细的教程可以在 文档 中找到。

接下来制作 wheel 包,命令很简单:

python3 setup.py sdist bdist_wheel

这样会将我们的代码打包到 dist/ 目录下。

我们需要将制作好的程序发布到 pypi 上,安装好 twine: pip install twine, 在 $HOME 目录(linux) 或者 C:\Users\yourname\ 目录(windows)下新建一个 .pypirc 文件,输入下面的内容:

[distutils]
index-servers=
pypi
testpypi [pypi]
repository: https://upload.pypi.org/legacy/
username: yourname
password: yourpasswd [testpypi]
repository: https://test.pypi.org/legacy/
username: yourname
password: yourpasswd

接下来就可以发布了,但是不要着急,正式发布前都请将制作好的包发布到 testpypi 上:

python3 -m twine upload -r testpypi dist/* --skip-existing

等你确认一切无误后,在将 -r testpypi 参数换成 -r pypi 以便正式发布。

P4: 小结

  • 尽管制作一个小巧、实用而且完备的 Python 脚本很有意思,但是要想做出一个实用、对用户友好的脚本,还是需要花一些时间的。实际上如果要为用户提供更加友好的实用方式,可以考虑以界面的形式为用户提供操作,比如说以网页的形式提供操作,在服务器后台对用户操作进行处理。不过你也可能注意到了,这个脚本的功能其实就是很基础的增删查改。

  • 上面示例的 Python 脚本在使用过程中会用到超级用户权限,为了防止操作失败,在添加、删除仓库时必须提供超级用户权限,否则脚本将会退出,还有一种方式可以在运行时获得超级用户权限,就是使用 [elevate][https://github.com/barneygale/elevate] 进行提权,具体的使用方式还没来得及看,不过我猜测应该需要向 elevate 提供一个超级用户密码的文件。


如果你想仔细查看上述 Python 示例代码的话,可以在 Github 上找到 brifuture-facilities 项目的代码。如果你觉得我的文章或者其中的代码对你有帮助,请给它点个赞。

参考

掘金:Python中最好用的命令行参数解析工具

cnblogs:搭建简单的Git服务器

cnblogs:lighttpd 与 gitweb 搭建服务器

setuptools documentation

使用 Python 编写脚本并发布的更多相关文章

  1. python编写脚本应用实例

    这里主要记录工作中应用python编写脚本的实例.由于shell脚本操作数据库(增.删.改.查)并不是十分直观方便,故这里采用python监控mysql状态,然后将状态保存到数据库中,供前台页面进行调 ...

  2. 【MonkeyRunner】[技术博客]用python编写脚本查看设备信息

    [MonkeyRunner]用python编写脚本查看设备信息 原以为是个简单的操作,在实践的时候发现了一些问题. python脚本 test.py: from com.android.monkeyr ...

  3. python编写脚本,删除固定用户下的所有表

    脚本如下: [oracle@ycr python]$ more t_del.py #/usr/bin/python#coding:utf8 import sysimport cx_Oracle i=0 ...

  4. Zabbix调用外部脚本发送邮件:python编写脚本

    Zabbix调用外部脚本发送邮件的时候,会在命令行传入两个参数,第一个参数就是要发送给哪个邮箱地址,第二个参数就是邮件信息,为了保证可以传入多个参数,所以假设有多个参数传入 #!/usr/bin/en ...

  5. Python编写脚本(输出三星形状的‘*’符号)

    环境:python3.* 心得:个人认为脚本非我强项,以下效果可以有更简单解决方案,纯属练习逻辑. 方案一: s=1 while s<=10: #这是决定多少列,起始为1,大循环一圈即加一,就是 ...

  6. python编写脚本

    #!/usr/bin/env python #-*- coding:utf-8 -*- import sys import os from subprocess import Popen,PIPE c ...

  7. python编写脚本,登录Github通过指定仓库指定敏感关键字搜索自动化截图生成文件【完美截图】

    前言:为了避免开发人员将敏感信息写入文件传到github,所以测试人员需要检查每个仓库是否有写入,人工搜索审核比较繁琐,所以写一个脚本通过配置 配置文件,指定需要搜索的仓库和每个仓库需要搜索的关键字, ...

  8. python编写网络抓包分析脚本

    python编写网络抓包分析脚本 写网络抓包分析脚本,一个称手的sniffer工具是必不可少的,我习惯用Ethereal,简单,易用,基于winpcap的一个开源的软件 Ethereal自带许多协议的 ...

  9. python编写文件统计脚本

    python编写文件统计脚本 思路:用os模块中的一些函数(os.listdir().os.path.isdir().os.path.join().os.path.abspath()等) 实现功能:显 ...

随机推荐

  1. jenkins修改时区

    查看jenkins目前的时区 访问http://your-jenkins/systemInfo,查看user.timezone变量的值 默认是纽约时间 修改时区 查https://wiki.jenki ...

  2. 指定的 LINQ 表达式包含对与不同上下文关联的查询的引用

    解决方法是分两次查询. 报错的原因是在涉及到内存中的对象与EF里的对象混合查询时,内存中的对象要是基元类型. 第一次查询实际上会因为EF的延时加载,不会立即将数据查询到内存中. 解决方法是对第一次查询 ...

  3. ES6摘抄

    1.函数可选参数function log(x, y = 'World') {} 只能作为尾参数使用,因为如果不是尾参数还是要输入的.2.函数参数默认值与解构赋值结合使用.(注意对象冒号解构等号)fun ...

  4. asp.net web 应用站点支持域账户登录

    1.IIS站点应用程序池设置管道模式为classic模式,identity设置为管理员账户 2.站点验证设置,只打开windows验证,其他都关闭 3.应用程序配置web.config配置如下: &l ...

  5. POJ的练习题

    http://wenku.baidu.com/link?url=PT1gkBWC3eXuzzs0QqWklC0VNYkf5ynxBFguXPGYR22l1D2tXmQ4VjnsWvbFyvj1fqGi ...

  6. 【ocp-12c】最新Oracle OCP-071考试题库(44题)

    44.(9-12)choose all that apply View the Exhibit and examine the details of the ORDER_ITEMS table. Ev ...

  7. JS 获取各个偶数之和!!

    <html>    <head>        <meta charset="utf-8" />        <title>js& ...

  8. day 28 :进程相关,进程池,锁,队列,生产者消费者模式

    ---恢复内容开始--- 前情提要: 一:进程Process  1:模块介绍 from multiprocessing import Process from multiprocessing impo ...

  9. Java多线程——对象组合

    我们不希望对每一次的内存访问都进行分析以确保程序是线程安全的,而是希望将一些现有的线程安全组件组合为更大规模的组件或者程序,这里介绍一些组合模式,这些组合模式能够使一个类更容易成为线程安全的,并且在维 ...

  10. [Alpha]团队任务拆解

    要求 团队任务拆解 Alpha阶段总体规划 初步实现测试.报告: 实现对游戏最基本的测试,包括内置随机测试.提供可供选择的组合测试 实现对游戏测试时操作的记录并最终生成报告 能够在发现异常时及时将异常 ...