从 Python 执行 shell 命令并合并环境更改(没有子进程)?

From Python execute shell command and incorporate environment changes (without subprocess)?

我正在探索使用 iPython 作为 shell 替代需要良好日志记录和操作重现性的工作流程。

我有一些非 python 二进制程序和 bash shell 命令 运行 在我的常用工作流中操纵影响后续工作的环境变量。即当 运行 从 bash 时,环境发生变化。

如何将这些案例合并到 Python / iPython 交互式 shell 中并修改会话中的环境?

让我们关注最关键的案例。 从 bash,我会做:

> sysmanager initialize foo

其中 sysmanager 是一个函数:

> type sysmanager
sysmanager is a function
sysmanager () 
{ 
  eval `/usr/bin/sysmanagercmd bash $*`
}

我不控制二进制文件 sysmanagercmd,它通常会对环境变量进行重要的操作。使用 eval 内置意味着这些操作会影响 shell 进程向前发展——这对设计至关重要。

如何从 Python / iPython 调用此命令具有相同的效果? python 是否有等同于 bash 的 eval 内置非 python 命令的东西?

由于没有遇到任何内置功能来执行此操作,我编写了以下函数来实现广泛的意图。环境变量的修改和工作目录的改变反映在函数returns之后的pythonshell中。 shell 别名或函数的任何修改 不会 保留,但也可以通过增强此功能来完成。

#!/usr/bin/env python3

"""
Some functionality useful when working with IPython as a shell replacement.
"""


import subprocess
import tempfile
import os

def ShellEval(command_str):
    """
    Evaluate the supplied command string in the system shell.
    Operates like the shell eval command:
       -  Environment variable changes are pulled into the Python environment
       -  Changes in working directory remain in effect
    """
    temp_stdout = tempfile.SpooledTemporaryFile()
    temp_stderr = tempfile.SpooledTemporaryFile()
    # in broader use this string insertion into the shell command should be given more security consideration
    subprocess.call("""trap 'printf "\0`pwd`\0" 1>&2; env -0 1>&2' exit; %s"""%(command_str,), stdout=temp_stdout, stderr=temp_stderr, shell=True)
    temp_stdout.seek(0)
    temp_stderr.seek(0)
    all_err_output = temp_stderr.read()
    allByteStrings = all_err_output.split(b'\x00')
    command_error_output = allByteStrings[0]
    new_working_dir_str  = allByteStrings[1].decode('utf-8') # some risk in assuming index 1. What if commands sent a null char to the output?

    variables_to_ignore = ['SHLVL','COLUMNS', 'LINES','OPENSSL_NO_DEFAULT_ZLIB', '_']

    newdict = dict([ tuple(bs.decode('utf-8').split('=',1)) for bs in allByteStrings[2:-1]])
    for (varname,varvalue) in newdict.items():
        if varname not in variables_to_ignore:
            if varname not in os.environ:
                #print("New Variable: %s=%s"%(varname,varvalue))
                os.environ[varname] = varvalue
            elif os.environ[varname] != varvalue:
                #print("Updated Variable: %s=%s"%(varname,varvalue))
                os.environ[varname] = varvalue
    deletedVars = []
    for oldvarname in os.environ.keys():
        if oldvarname not in newdict.keys():
            deletedVars.append(oldvarname)
    for oldvarname in deletedVars:
        #print("Deleted environment Variable: %s"%(oldvarname,))
        del os.environ[oldvarname]

    if os.getcwd() != os.path.normpath(new_working_dir_str):
        #print("Working directory changed to %s"%(os.path.normpath(new_working_dir_str),))
        os.chdir(new_working_dir_str)
    # Display output of user's command_str.  Standard output and error streams are not interleaved.
    print(temp_stdout.read().decode('utf-8'))
    print(command_error_output.decode('utf-8'))