看 pip 的启动脚本看得我人都傻了,还能把 shell 和 py 揉到一起写

2022-07-26 16:58:24 +08:00
 MiketsuSmasher

/usr/bin/pip3.10

#!/bin/sh
"exec" "$(dirname $0)/python3.10" "$0" "$@"
# -*- coding: utf-8 -*-
import re
import sys
from pip._internal.cli.main import main
if __name__ == '__main__':
    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
    sys.exit(main())

个人理解是,上述脚本通过 exec ,用指定位置的 python 直接替换掉了当前的 shell 进程,那么为什么 "exec" "$(dirname $0)/python3.10" "$0" "$@" 之后的代码还能接着执行呢?

以及,为什么要这样写呢,拆开写不好吗?

5133 次点击
所在节点    Linux
26 条回复
qwq11
2022-07-26 17:07:21 +08:00
因为他 exec 启动 py 来执行当前脚本,第二行是一坨字符串直接被 py 忽略,然后 py 就正常往下执行
ruanimal
2022-07-26 17:10:00 +08:00
其实就是用 shell 启动了 python

$0 是当前文件路径
$@ 是所有命令行参数

"exec" 这一行是字符串,在 python 解释器中是没有效果的
AoEiuV020CN
2022-07-26 17:10:51 +08:00
> 之后的代码还能接着执行呢?
shell 进程就到此为止了,没有接着执行,
exec 创建了 python 进程才真正执行后面的代码,

就是为了强制使用"$(dirname $0)/python3.10",不用管 python 到底在哪里吧,
qwq11
2022-07-26 17:13:34 +08:00
至于为什么混在一起写,我猜是因为,分成两个文件(pip.shpip.py)会比较迷惑人。本来 py 版本管理就乱,一个 py 还整两个 pip 入口
BeautifulSoap
2022-07-26 17:19:02 +08:00
论 shell 脚本有多丑多难看,但又多好用
geelaw
2022-07-26 17:19:36 +08:00
https://en.wikipedia.org/wiki/Polyglot_(computing)

好处是在 shell 脚本里你既可以写 pip foo 也可以写 python pip foo ,前者的效果就是 python pip foo 。后面的代码当然没有“接着”执行,因为 shell script interpreter 进程已经被替换了,替换后的进程执行了其他代码,而这个其他的代码,刚好就是同一份,而且替换后的进程是按 Python 解读这份代码。
MiketsuSmasher
2022-07-26 18:25:40 +08:00
@qwq11
@ruanimal
@AoEiuV020CN
@qwq11
感谢解惑

@geelaw 是我见识少了,C & bash & PHP 的杂糅让我叹为观止🤣
webcape233
2022-07-26 19:24:33 +08:00
实在是骚 学会了
hsfzxjy
2022-07-26 19:26:09 +08:00
妙啊
kokutou
2022-07-26 19:36:38 +08:00
ysc3839
2022-07-26 19:43:32 +08:00
大部分 shell 是逐行解析的,所以只需要在解析到别的语言的代码前结束运行就不会出现错误。同时另一种脚本语言要有某种机制跳过开头的脚本,一般是想办法让其解析成注释。楼主给的例子,有可能是 Python 会从 coding: utf-8 之后执行,跳过之前的代码。

举个例子,cmd 脚本和 PowerShell 脚本写在同一个文件内,主要用于解决 PowerShell 脚本不能直接运行的问题:
```
<# :
@echo this is from cmd!
@powershell -NoProfile -Command "Invoke-Expression (${%~f0} | Out-String)"
@pause
@exit /b
#>
Write-Host this is from powershell!
```

原理是利用 cmd 允许(其实大部分 shell 也都允许)重定向出现在一行中的任意位置,开头的 <# : 经过处理后去掉了重定向,就只剩下一个冒号了。而冒号在 cmd 中是标签,不会执行任何动作,于是第一行什么事都不会做,也符合语法。最后 exit 退出,cmd 就不会继续解析后面的代码了。到了 PowerShell 执行,开头这块 <# #> 是注释,就直接跳过了。
chenxytw
2022-07-26 20:58:36 +08:00
其实我更好奇 OP 用的是什么发行版,什么包管理器。
chenxytw
2022-07-26 20:59:32 +08:00
@chenxytw 这个内容和印象中正常途径打包出来的不一样....多了 sh 处理的部分 Orz
24bit
2022-07-26 21:19:04 +08:00
在另一个脚本语言的某个脚本中见过这种写法,挺巧妙的
Nitroethane
2022-07-26 21:57:34 +08:00
@chenxytw 至少不是 Arch ,Arch 默认安装的 python 中的 pip 是纯 py 代码
qbqbqbqb
2022-07-26 22:11:46 +08:00
@ysc3839 Python 不会从 coding: utf-8 之后执行。

Python 能正确跳过上面那行 bash 的原因,是因为编写的时候故意加了双引号,Python 就把它当成字符串了。单写一个字符串,但又不赋值给变量,也不写在表达式或函数里,当然对程序执行流程没有影响了。

不然的话,如果仅仅是编写 bash 脚本,“exec”没必要加双引号。
Firxiao
2022-07-26 22:20:48 +08:00
#!/bin/sh
"exec" "$(dirname $0)/python3.10" "$0" "$@"

#!/usr/bin/env python3.10
等效

其实就是定义下去哪加载 Python
楼主可以想下 如何用 shell 运行 Python 脚本 比如 ./hello.py 该怎么搞? 哈哈
ysc3839
2022-07-26 22:24:38 +08:00
@Firxiao 两者并不等效,dirname $0 是取当前脚本文件所在目录,执行的是和脚本文件同目录的 python3.10 。而 env 会查找 PATH 环境变量来执行对应程序。
Firxiao
2022-07-26 22:37:59 +08:00
@ysc3839 等效指的是 都是去指定 Python 当然如你所说指定的不一样而已 不过 第二种方式楼主应该会比较好理解 😄
lovelylain
2022-07-26 23:34:44 +08:00
这种写法应该是改进版,可以随意放置 python 目录,方便打包发布。我记得几年以前我用 pyvenv 生成的执行环境,第一行是写死的 python 完整路径,这就导致打包到其他机器必须保持路径完全一样,但

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/868827

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX