使用加载的 rcfile 调用子 shell 命令

使用加载的 rcfile 调用子 shell 命令

我有一些Python代码可以做到:(这是一个简化版本)

shell = os.environ['SHELL']
os.environ['PATH'] = ... # some new PATH value
if call(shell, '-c', 'program_that_checks_that_PATH_is_sane'): # hasn't been mangled by the rcfile
    sys.exit('fix your .bashrc|.zshrc|.config/fish')
call(shell) # drops the user in a shell

我愚蠢地认为这$SHELL -c足以满足我的目的,但我最近发现 PATH 检查在 bash 上不起作用

我试着看一些建议的解决方案,但是 bash 比我预想的要脆弱得多

基本上,我发现,如果源文件可以触发某些错误,BASH_ENV则手动源将不起作用bash -c "source ~/.bashrc; do_your_stuff"

如果以交互方式调用 bash,这些相同的错误不是问题(即,bash -i将忽略错误,并且bash -c将默默地失败)。但是bash -i不是一个可行的解决方案(我猜这是因为bash -i会“窃取”标准输入,从而停止调用python进程)

解决方案也不能简单地是“不要使用损坏的 ~/.bashrc”:

  • 面对配置不佳的环境,代码必须具有鲁棒性
  • 这种情况发生在默认情况下.bashrc,例如ubuntu 提供的一个(这是旧版本)

错误实际上发生在这一行set -xe内部(在采购之前发现它) :(这怎么会失败,采购过程超出了我的范围)/usr/share/bash-completion/bash_completion[[ -f /etc/slackware-version ]] && sysvdirs=( /etc/rc.d )

经过大量调整后,我发现如何在采购时检测到此类错误:

set -e && . ~/.bashrc & wait %% ; echo $?

奇怪的是,以下操作会失败:

set -e ; . ~/.bashrc & wait %% ; echo $?

尽管如此,这还不够好,因为两者都不是

bash -c "set -e && . ~/.bashrc & wait %% ; echo \$?"
call(['bash', '-c', 'set -e && . ~/.bashrc & wait %% ; echo $?'])

将打印非零退出代码。我还希望避免依赖这样的 shell 特定代码。

如何确保在调用时正确加载bash -c.bashrc

作为整个问题的替代方案,我正在考虑修改 .{bash,zsh}rc 来识别 PATH 的问题。这是一个糟糕的解决方案,但它可以覆盖 90% 的情况,并且避免分叉额外的进程。

import re
r = '^export PATH\s?=\s?([^:]+:)+(\$PATH|\${PATH})'
g = re.match(r, 'export PATH=/usr/bin:$PATH').groups()
any(check_collision(x[:-1]) for x in g[:-1])

编辑:

bash-i标志似乎是最简单、最有前途的半解决方案,但它的行为并不明显,一些例子可以帮助人们理解:

这会运行 ls,然后让我们进入 Python 提示符,Python 正在运行

python3 -ic "import os; os.system(\"bash -c ls\")"

这会停止 Python 进程

python3 -ic "import os; os.system(\"bash -ic ls\")"

为子进程提供不同的标准输入是不够的:

python3 -ic "import subprocess;f=open('/dev/null');subprocess.call(['bash', '-ic', 'ls'], stdin=f)"

最后,bash -ic实际上并没有从标准输入获取额外的命令,以下两个命令具有完全相同的行为:

 echo echo sed | bash -c "sed 's/[^ ]*$/bash/'"
 echo echo sed | bash -ic "sed 's/[^ ]*$/bash/'"

答案1

不要SHELL在脚本中使用环境变量。这是用户首选的交互式 shell。您对它支持的语法一无所知。可能是 tcsh、zsh、fish、rc……

不要.bashrc从脚本加载。用户可能会放置依赖 bash 进行交互并具有终端的东西;它可能会挂起,或重新配置您的终端,或隐藏脚本的输出,或许多不好的事情。当您运行时bash -c ….bashrc不会加载,这是设计使然的。

如果您需要检查是否PATH正常,可以在 Python 中执行,无需为此调用 shell。如果“代码必须在面对配置不良的环境时保持健壮”,您的意思是您的 Python 脚本应该尝试复制交互式 shell 的 PATH,即使它不是从一个 shell 启动的,那么就不要这样做:您还差得很远。更有可能以奇怪的方式搞砸(特别是覆盖用户的显式设置),而不是帮助用户。

如果您的脚本由于某种原因将用户放入交互式 shell,则执行 run ,如果未设置环境变量则os.environ['SHELL']返回到。pwd.getpwuid(os.geteuid()).pw_shell如果您的脚本在终端中运行,这将运行一个交互式 shell,您不需要尝试猜测-i或其他选项,这些选项可能意味着也可能不意味着您希望的内容。如果您的脚本没有在终端中运行,那么它就不会尝试与用户交互,也不应该启动交互式 shell。

相关内容