V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Distributions
Ubuntu
Fedora
CentOS
中文资源站
网易开源镜像站
MiketsuSmasher
V2EX  ›  Linux

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

  •  
  •   MiketsuSmasher · 2022-07-26 16:58:24 +08:00 · 5332 次点击
    这是一个创建于 860 天前的主题,其中的信息可能已经有所发展或是发生改变。

    /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" "$@" 之后的代码还能接着执行呢?

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

    26 条回复    2022-07-27 10:34:56 +08:00
    qwq11
        1
    qwq11  
       2022-07-26 17:07:21 +08:00 via Android
    因为他 exec 启动 py 来执行当前脚本,第二行是一坨字符串直接被 py 忽略,然后 py 就正常往下执行
    ruanimal
        2
    ruanimal  
       2022-07-26 17:10:00 +08:00   ❤️ 1
    其实就是用 shell 启动了 python

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

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

    就是为了强制使用"$(dirname $0)/python3.10",不用管 python 到底在哪里吧,
    qwq11
        4
    qwq11  
       2022-07-26 17:13:34 +08:00 via Android
    至于为什么混在一起写,我猜是因为,分成两个文件(pip.shpip.py)会比较迷惑人。本来 py 版本管理就乱,一个 py 还整两个 pip 入口
    BeautifulSoap
        5
    BeautifulSoap  
       2022-07-26 17:19:02 +08:00
    论 shell 脚本有多丑多难看,但又多好用
    geelaw
        6
    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
        7
    MiketsuSmasher  
    OP
       2022-07-26 18:25:40 +08:00
    @qwq11
    @ruanimal
    @AoEiuV020CN
    @qwq11
    感谢解惑

    @geelaw 是我见识少了,C & bash & PHP 的杂糅让我叹为观止🤣
    webcape233
        8
    webcape233  
       2022-07-26 19:24:33 +08:00 via iPhone
    实在是骚 学会了
    hsfzxjy
        9
    hsfzxjy  
       2022-07-26 19:26:09 +08:00 via Android
    妙啊
    kokutou
        10
    kokutou  
       2022-07-26 19:36:38 +08:00
    ysc3839
        11
    ysc3839  
       2022-07-26 19:43:32 +08:00   ❤️ 1
    大部分 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
        12
    chenxytw  
       2022-07-26 20:58:36 +08:00
    其实我更好奇 OP 用的是什么发行版,什么包管理器。
    chenxytw
        13
    chenxytw  
       2022-07-26 20:59:32 +08:00
    @chenxytw 这个内容和印象中正常途径打包出来的不一样....多了 sh 处理的部分 Orz
    24bit
        14
    24bit  
       2022-07-26 21:19:04 +08:00
    在另一个脚本语言的某个脚本中见过这种写法,挺巧妙的
    Nitroethane
        15
    Nitroethane  
       2022-07-26 21:57:34 +08:00 via iPhone
    @chenxytw 至少不是 Arch ,Arch 默认安装的 python 中的 pip 是纯 py 代码
    qbqbqbqb
        16
    qbqbqbqb  
       2022-07-26 22:11:46 +08:00   ❤️ 3
    @ysc3839 Python 不会从 coding: utf-8 之后执行。

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

    不然的话,如果仅仅是编写 bash 脚本,“exec”没必要加双引号。
    Firxiao
        17
    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
        18
    ysc3839  
       2022-07-26 22:24:38 +08:00 via Android
    @Firxiao 两者并不等效,dirname $0 是取当前脚本文件所在目录,执行的是和脚本文件同目录的 python3.10 。而 env 会查找 PATH 环境变量来执行对应程序。
    Firxiao
        19
    Firxiao  
       2022-07-26 22:37:59 +08:00
    @ysc3839 等效指的是 都是去指定 Python 当然如你所说指定的不一样而已 不过 第二种方式楼主应该会比较好理解 😄
    lovelylain
        20
    lovelylain  
       2022-07-26 23:34:44 +08:00 via Android
    这种写法应该是改进版,可以随意放置 python 目录,方便打包发布。我记得几年以前我用 pyvenv 生成的执行环境,第一行是写死的 python 完整路径,这就导致打包到其他机器必须保持路径完全一样,但
    lovelylain
        21
    lovelylain  
       2022-07-26 23:36:22 +08:00 via Android
    这种写法应该是改进版,可以随意放置 python 目录,方便打包发布。我记得几年以前我用 pyvenv 生成的执行环境,第一行是写死的 python 完整路径,这就导致打包到其他机器必须保持路径完全一样,局限性很大,改成这种跟 shell 结合的写法,就不受这个限制了。妙!
    xujinkai
        22
    xujinkai  
       2022-07-26 23:41:30 +08:00
    想起来有个同事把升级包数据拼接在一个 shell 脚本后边,做成了一个自执行的升级程序
    MiketsuSmasher
        23
    MiketsuSmasher  
    OP
       2022-07-26 23:42:49 +08:00
    @Nitroethane 这个你真说错了,我给的代码就是来自 Arch 的 python-pip 包的 /usr/bin/pip
    MiketsuSmasher
        24
    MiketsuSmasher  
    OP
       2022-07-26 23:45:13 +08:00
    @Nitroethane 不好意思,刚刚看错了,Arch 的 pip 确实是纯 py
    MiketsuSmasher
        25
    MiketsuSmasher  
    OP
       2022-07-26 23:46:20 +08:00
    rev1si0n
        26
    rev1si0n  
       2022-07-27 10:34:56 +08:00
    骚操作,学到了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5310 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 03:46 · PVG 11:46 · LAX 19:46 · JFK 22:46
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.