Python、bash/python/subprocess进程(shell)之间的关系?
Python, the relationship between the bash/python/subprocess processes (shells)?
当尝试用 python 编写脚本时,我有一个基本的知识漏洞。
更新: 多亏了答案,我将 shell
一词更正为 process/subprocess
命名法
- 从 Bash 提示符开始,我们称其为
BASH_PROCESS
- 然后在
BASH_PROCESS
我 运行 python3 foo.py
中,python 脚本 运行 说 PYTHON_SUBPROCESS
- 在
foo.py
中是对 subprocess.run(...)
的调用,这个子进程命令 运行s 在 `SUBPROCESS_SUBPROCESS 中
- 在
foo.py
中是 subprocess.run(..., shell=True)
,这个子进程命令 运行s 在 SUBPROCESS_SUBPROCESS=True
中
测试 process/subprocess 是否相等
说 SUBPROCESS_A
开始 SUBPROCESS_B
。在下面的问题中,当我说 SUBPROCESS_A
== SUBPROCESS_B
时,我的意思是如果 SUBPROCESS_B
设置了一个环境变量,当它 运行 完成时,他们会在 SUBPROCESS_A
中设置 env 变量?如果 SUBPROCESS_B
中有一个 运行s eval "$(ssh-agent -s)"
,SUBPROCESS_A
现在也会有一个 ssh 代理吗?
问题
使用上述命名法和相等性测试
- 是
BASH_PROCESS
==PYTHON_SUBPROCESS
吗?
- 是
PYTHON_SUBPROCESS
== SUBPROCESS_SUBPROCESS
吗?
- 是
PYTHON_SUBPROCESS
== SUBPROCESS_SUBPROCESS=True
吗?
- 如果
SUBPROCESS_SUBPROCESS=True
不等于 BASH_PROCESS
,那么如何改变执行环境(例如 eval "$(ssh-agent -s)"
)以便 python 脚本可以设置来电者的环境?
None 这些等式是正确的,其中一半“shell”实际上不是 shell。
你的 bash shell 是 shell。当您从 shell 启动 Python 脚本时,运行 脚本的 Python 进程是 bash shell 的子进程过程。当您从 Python 脚本启动子进程时,该子进程是 Python 进程的子进程。如果您使用 shell=True
启动子进程,Python 调用 shell 来解析和 运行 命令,但除此之外,[=35] 不涉及 shell =]宁子进程。
子进程在启动时从其父进程继承环境变量(除非您采取特定步骤来避免这种情况),但它们不能为其父进程设置环境变量。您不能 运行 Python 脚本来设置 shell 中的环境变量,或者 运行 来自 Python 的子进程来设置 Python 脚本的环境变量。
您似乎混淆了这里的几个概念。
TLDR 不,子进程无法更改其父进程的环境。另见 Global environment variables in a shell script
你好像真的不是在问“shells”。
相反,这些是 子流程; 如果您 运行 python foo.py
在 shell 中,Python 流程是 [=] 的子流程125=] 过程。 (许多 shell 允许您 exec python foo.py
将 shell 进程替换为 Python 进程;此进程现在是启动 shell 的进程的子进程。在 Unix-like 系统上,最终所有进程都是进程 1 的后代,即 init
进程。)
subprocess
运行 简单来说就是一个子进程。如果 shell=True
则 Python 的直接子进程是 shell,而您 运行 的命令是 shell 的子进程。 shell 将是默认值 shell(Windows 上的 cmd
,Unix-like 系统上的 /bin/sh
),尽管您可以使用例如显式覆盖它executable="/bin/bash"
示例:
subprocess.Popen(['printf', '%s\n', 'foo', 'bar'])
Python是父进程,printf
是父进程是Python的子进程。
subprocess.Popen(r"printf '%s\n' foo bar", shell=True)
Python是/bin/sh
的父进程,而/bin/sh
又是printf
的父进程。当 printf
终止时,sh
也会终止,因为它已到达其脚本的末尾。
也许注意到 shell 负责解析命令行并将其拆分为四个标记,我们最终在上一个示例中显式地直接传递给 Popen
。
您 运行 的命令可以访问 shell 通配符扩展、管道、重定向、引用、变量扩展、后台处理等功能
在这个孤立的示例中,使用了其中的 none 个,因此您基本上是在添加一个不必要的进程。 (如果您想避免将命令拆分为标记的轻微负担,可以使用 shlex.split()
。)另请参阅 Actual meaning of 'shell=True' in subprocess
subprocess.Popen(r"printf '%s\n' foo bar", shell=True, executable="/bin/bash")
Python是Bash的父进程,而Bash又是printf
的父进程。除了 shell 的名称外,这与前面的示例相同。
在某些情况下,当您要执行的命令需要 Bash 中可用的功能时,您确实需要更慢且更多的 memory-hungry Bash shell ,但不在谍影重重 shell。一般来说,更好的解决方案几乎总是 运行 在子进程中尽可能少的代码,而不是用原生 Python 构造替换那些 Bash 命令;但如果您知道自己在做什么(或者 真的 不知道自己在做什么,但需要完成工作而不是正确解决问题),该工具可能会有用.
(另外,您应该尽可能避免裸露 Popen
,如 subprocess
文档中所述。)
子进程在启动时会继承其父进程的环境。在 Unix-like 系统上,进程无法更改其父进程的环境(尽管父进程可能参与使之成为可能,如您的 eval
示例)。
为了完成您最终可能要问的问题,您可以在 Python 中设置一个环境,然后将您的其他命令作为子进程启动,也许然后使用显式 env=
关键字参数指向您希望它使用的环境:
import os
...
env = os.environ.copy()
env["PATH"] = "/opt/foo:" + env["PATH"]
del env["PAGER"]
env["secret_cookie"] = "xyzzy"
subprocess.Popen(["otherprogram"], env=env)
或让 Python 打印出可以安全地传递给 Bourne shell 中的 eval
的表单中的值。 (注意:这需要您了解 eval
的一般风险,特别是目标 shell 的引用约定;此外,您可能需要支持多个 shell,除非您只针对非常有限的受众。)
... 虽然在许多情况下,到目前为止最简单的解决方案是在 shell 中设置环境,然后 运行 Python 作为 shell 实例(或 exec python
,如果你想在 shell 实例完成它的部分后摆脱它;另见 What are the uses of the exec command in shell scripts?)
Python 不带参数启动 Python REPL,它可以被视为“shell”,尽管我们通常不会使用该术语(也许改为称它为“交互式口译员”——另见下文);但是 python foo.py
只是 运行 脚本 foo.py
并退出,所以那里没有 shell。
“shell”的定义有点context-dependent,但你这里好像真的不是在问shell。 (一些 GUI 有“图形 shell” 等概念,但我们已经超出了您要询问的范围。)一些程序是命令解释器(Python 可执行文件解释并执行Python 语言中的命令;Bourne shell 解释和执行 shell 脚本)但通常只有那些主要目的包括 运行 其他程序的程序才被称为“shells".
当尝试用 python 编写脚本时,我有一个基本的知识漏洞。
更新: 多亏了答案,我将 shell
一词更正为 process/subprocess
命名法
- 从 Bash 提示符开始,我们称其为
BASH_PROCESS
- 然后在
BASH_PROCESS
我 运行python3 foo.py
中,python 脚本 运行 说PYTHON_SUBPROCESS
- 在
foo.py
中是对subprocess.run(...)
的调用,这个子进程命令 运行s 在 `SUBPROCESS_SUBPROCESS 中
- 在
foo.py
中是subprocess.run(..., shell=True)
,这个子进程命令 运行s 在SUBPROCESS_SUBPROCESS=True
中
测试 process/subprocess 是否相等
说 SUBPROCESS_A
开始 SUBPROCESS_B
。在下面的问题中,当我说 SUBPROCESS_A
== SUBPROCESS_B
时,我的意思是如果 SUBPROCESS_B
设置了一个环境变量,当它 运行 完成时,他们会在 SUBPROCESS_A
中设置 env 变量?如果 SUBPROCESS_B
中有一个 运行s eval "$(ssh-agent -s)"
,SUBPROCESS_A
现在也会有一个 ssh 代理吗?
问题
使用上述命名法和相等性测试
- 是
BASH_PROCESS
==PYTHON_SUBPROCESS
吗? - 是
PYTHON_SUBPROCESS
==SUBPROCESS_SUBPROCESS
吗? - 是
PYTHON_SUBPROCESS
==SUBPROCESS_SUBPROCESS=True
吗? - 如果
SUBPROCESS_SUBPROCESS=True
不等于BASH_PROCESS
,那么如何改变执行环境(例如eval "$(ssh-agent -s)"
)以便 python 脚本可以设置来电者的环境?
None 这些等式是正确的,其中一半“shell”实际上不是 shell。
你的 bash shell 是 shell。当您从 shell 启动 Python 脚本时,运行 脚本的 Python 进程是 bash shell 的子进程过程。当您从 Python 脚本启动子进程时,该子进程是 Python 进程的子进程。如果您使用 shell=True
启动子进程,Python 调用 shell 来解析和 运行 命令,但除此之外,[=35] 不涉及 shell =]宁子进程。
子进程在启动时从其父进程继承环境变量(除非您采取特定步骤来避免这种情况),但它们不能为其父进程设置环境变量。您不能 运行 Python 脚本来设置 shell 中的环境变量,或者 运行 来自 Python 的子进程来设置 Python 脚本的环境变量。
您似乎混淆了这里的几个概念。
TLDR 不,子进程无法更改其父进程的环境。另见 Global environment variables in a shell script
你好像真的不是在问“shells”。
相反,这些是 子流程; 如果您 运行 python foo.py
在 shell 中,Python 流程是 [=] 的子流程125=] 过程。 (许多 shell 允许您 exec python foo.py
将 shell 进程替换为 Python 进程;此进程现在是启动 shell 的进程的子进程。在 Unix-like 系统上,最终所有进程都是进程 1 的后代,即 init
进程。)
subprocess
运行 简单来说就是一个子进程。如果 shell=True
则 Python 的直接子进程是 shell,而您 运行 的命令是 shell 的子进程。 shell 将是默认值 shell(Windows 上的 cmd
,Unix-like 系统上的 /bin/sh
),尽管您可以使用例如显式覆盖它executable="/bin/bash"
示例:
subprocess.Popen(['printf', '%s\n', 'foo', 'bar'])
Python是父进程,
printf
是父进程是Python的子进程。subprocess.Popen(r"printf '%s\n' foo bar", shell=True)
Python是
/bin/sh
的父进程,而/bin/sh
又是printf
的父进程。当printf
终止时,sh
也会终止,因为它已到达其脚本的末尾。也许注意到 shell 负责解析命令行并将其拆分为四个标记,我们最终在上一个示例中显式地直接传递给
Popen
。您 运行 的命令可以访问 shell 通配符扩展、管道、重定向、引用、变量扩展、后台处理等功能
在这个孤立的示例中,使用了其中的 none 个,因此您基本上是在添加一个不必要的进程。 (如果您想避免将命令拆分为标记的轻微负担,可以使用
shlex.split()
。)另请参阅 Actual meaning of 'shell=True' in subprocesssubprocess.Popen(r"printf '%s\n' foo bar", shell=True, executable="/bin/bash")
Python是Bash的父进程,而Bash又是
printf
的父进程。除了 shell 的名称外,这与前面的示例相同。在某些情况下,当您要执行的命令需要 Bash 中可用的功能时,您确实需要更慢且更多的 memory-hungry Bash shell ,但不在谍影重重 shell。一般来说,更好的解决方案几乎总是 运行 在子进程中尽可能少的代码,而不是用原生 Python 构造替换那些 Bash 命令;但如果您知道自己在做什么(或者 真的 不知道自己在做什么,但需要完成工作而不是正确解决问题),该工具可能会有用.
(另外,您应该尽可能避免裸露 Popen
,如 subprocess
文档中所述。)
子进程在启动时会继承其父进程的环境。在 Unix-like 系统上,进程无法更改其父进程的环境(尽管父进程可能参与使之成为可能,如您的 eval
示例)。
为了完成您最终可能要问的问题,您可以在 Python 中设置一个环境,然后将您的其他命令作为子进程启动,也许然后使用显式 env=
关键字参数指向您希望它使用的环境:
import os
...
env = os.environ.copy()
env["PATH"] = "/opt/foo:" + env["PATH"]
del env["PAGER"]
env["secret_cookie"] = "xyzzy"
subprocess.Popen(["otherprogram"], env=env)
或让 Python 打印出可以安全地传递给 Bourne shell 中的 eval
的表单中的值。 (注意:这需要您了解 eval
的一般风险,特别是目标 shell 的引用约定;此外,您可能需要支持多个 shell,除非您只针对非常有限的受众。)
... 虽然在许多情况下,到目前为止最简单的解决方案是在 shell 中设置环境,然后 运行 Python 作为 shell 实例(或 exec python
,如果你想在 shell 实例完成它的部分后摆脱它;另见 What are the uses of the exec command in shell scripts?)
Python 不带参数启动 Python REPL,它可以被视为“shell”,尽管我们通常不会使用该术语(也许改为称它为“交互式口译员”——另见下文);但是 python foo.py
只是 运行 脚本 foo.py
并退出,所以那里没有 shell。
“shell”的定义有点context-dependent,但你这里好像真的不是在问shell。 (一些 GUI 有“图形 shell” 等概念,但我们已经超出了您要询问的范围。)一些程序是命令解释器(Python 可执行文件解释并执行Python 语言中的命令;Bourne shell 解释和执行 shell 脚本)但通常只有那些主要目的包括 运行 其他程序的程序才被称为“shells".