文章

如何在命令行中用程序名调用python程序

问题

目前有一个工具库,里面有一些自己写的脚本,用来检测字幕中字体是否安装、转换笔记仓库中一些要发表的文档到个人网站仓库、选择pdf文档特定章节或页数并打印……

要调用这些工具,都需要:

  1. 进入脚本目录
  2. python a.py arg1 arg2

更麻烦的是,由于使用了虚拟环境,还要先切换到虚拟环境。

为此,又写了一个脚本tools.sh作为工具库的入口。执行时,先激活虚拟环境,然后等待用户输入需要执行的程序名和参数,执行完毕再等待用户输入下一条。将这个脚本所在路径加入PATH中,这样在任何一个目录下输入tools.sh即可。windows中安装git bash,就能跨平台通用。

但仍然存在问题:

  • 执行tools.sh后,通过shell的read获取用户输入及其不方便
    • 不能自动补全
    • 不能使用左右键移动
  • 参数中含有空格时,如文件路径,处理起来简直是灾难

总是,这一套方式及其不好用,弄了半天最后还是每次打开pycharm,然后输命令。

最近忽然想到,yt-dlp这样的通过pip安装的程序,不是可以直接在命令行中输入程序名进行调用,我要实现的不就是一样的功能。

官方提供了distutils工具包,供用户打包程序,打包后可以个人安装使用,也能上传到pypi。但官网上说,由于第三方工具包应用更广泛,python3.12将移除distutils。

其中提到的setuptools用起来十分简单,官网的教程也很清晰。

setuptools简易使用

安装

安装setuptools

1
pip install --upgrade setuptools

安装build,用于打包成whl文件,不装也行

1
pip install --upgrade build

配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
tools
├── README.md
├── args.py
├── clash_rule.py
├── default_settings.yml
├── dianfei.py
├── font_detect.py
├── jekyll_maker.py
├── life_pulse.py
├── monthly_checker.py
├── obsidian_metadata.py
├── oss.py
├── pitch_practice.py
├── printer.py
├── requirements.txt
├── run.sh
├── settings.py
├── setup.py
├── sub_extractor.py
├── sub_trans.py
├── test.py
├── tools.sh
├── utils.py
└── video_merger.py

我的目录结构非常简单,所有的脚本、配置文件、工具类都放在一个层级。

要进行打包,需要新增一个文件,告诉setuptools哪些需要打包、如何打包。有三种等价的格式pyproject.tomlsetup.cfgsetup.py,由于我这里需要从requirements.txt读取依赖的第三方库,因此使用setup.py

添加setup.py

1
2
3
4
5
6
7
8
9
10
from setuptools import setup


REQUIREMENTS = list(filter(None, open('requirements.txt', encoding='utf8').readlines()))

setup(
    name='tools',
    version='0.0.1',
    install_requires=REQUIREMENTS,
)

setup函数中传入读取出的requirements.txt中的依赖库名。

进行构建

测试能否构建成功,需要在第一步中安装build,未安装可跳过。

1
python -m build

恭喜,顺利报错。

1
FileNotFoundError: [Errno 2] No such file or directory: 'requirements.txt'

为什么会没有这个文件呢?明明当前目录下就有。

添加数据文件

原因在于打包的时候默认只将py文件复制过去,这些txtyml数据文件要打包的话需要手动指定。

  1. 开启包含数据文件选项
1
2
3
4
5
setup(
    # ...
    include_package_data=True,
    # ...
)

这一选项实际上默认开启的,不用管。

  1. 新增一个MANIFEST.in文件,在里面写明要包括哪些文件。
1
2
include *.txt
include *.yml

这样,项目目录下所有txt、yml文件都会打包进去(不包括子目录下的)。现在就能正常构建了。

增加执行入口

最终的目的不是打包,而是能通过输入程序名执行相关程序。需要指明程序的入口,将执行时的名字与代码中函数名对应起来。改动一下setup函数的参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
setup(
    name='tools',
    version='0.0.1',
    install_requires=REQUIREMENTS,

    entry_points={
        'console_scripts': [
            'font-detect = font_detect:main',
            'printer = printer:main',
            'jekyll-maker = jekyll_maker:main',
            'clash-rule = clash_rule:main',
            'pitch = pitch_practice:start',
        ]
    }
)

这样,命令行中输入font-detect,就会执行font_detect.pymain函数,非常方便。一个函数入口可以对应多个名称,一个脚本文件可以包含多个函数入口。

安装个人脚本库

以开发者模式进行安装,这样每次脚本改动后就能立即生效,免去了pip install的麻烦。

1
pip install --editable .

使用开发者模式相当于每次通过命令行运行的都是最新的程序代码,但这里的代码不包括setup.py本身,如果改动了setup.py,例如增加了console_scripts中的入口,还是需要重新安装。

测试一下,输入指令,正常执行!!!

1
2
3
$ font-detect
usage: font-detect [-h] [-d] file [file ...]
font-detect: error: the following arguments are required: file
本文由作者按照 CC BY 4.0 进行授权