了解 Shell 脚本中环境变量的范围和生命周期

了解 Shell 脚本中环境变量的范围和生命周期

我绞尽脑汁试图理解 shell 脚本和传递环境变量等等。

我正在尝试从 TextWrangler 执行一个 PHP 脚本,然后它会使用终端打开一个新的 PHP 脚本。(TextWrangler 是一个文本编辑器,它可以选择执行位于指定文件夹中的脚本,例如对当前活动文档进行操作)。

第一个脚本位于:

/Users/<username>/Library/Application Support/TextWrangler/Scripts/

...其内容如下:

#!/usr/bin/php
<?php
    var_dump( $_SERVER );
    chdir( __DIR__ );
    $file = realpath( '../Unix Support/test.php' );
    exec( sprintf( 'open -a Terminal "%s" &', $file ) );
    exit( 0 );
?>

第二个是:

/Users/<username>/Library/Application Support/TextWrangler/Unix Support/

...其内容如下:

#!/usr/bin/php
<?php
    var_dump( $_SERVER );
    exit( 0 );
?>

TextWrangler 将一些环境变量传递给第一个脚本(我可以通过 访问$_SERVER),并且它们符合预期。例如,TextWrangler 中当前活动的文档的正确文件路径。

第一次执行脚本时,环境变量也会自动正确传递给第二个脚本(我用 打开exec())。

现在到了令人沮丧的部分:当我在 TextWrangler 中切换活动文档并再次运行脚本时,第一个脚本再次从 TextWrangler 接收正确的环境变量,但第二个脚本仍然具有旧的环境变量,除非我事先已经关闭了终端。因此,显然终端会话以某种方式记住了第一个环境变量,并且不想更新。

但是除了这个非常基本的理解之外,我对环境变量传递如何工作、它们的范围等一无所知。那么,有人可以解释一下我如何才能做到(如果可能的话,首先)第二个脚本再次接收正确的环境变量,而不必每次都终止终端?

我尝试在调用之前在第一个脚本中明确设置环境变量exec(),如下所示:

foreach( $_SERVER as $key => $value )
{
    putenv( "$key=$value" );
}
exec( ... etc. );

我已经尝试在第一个和第二个脚本中最后取消设置环境变量,如下所示:

foreach( $_SERVER as $key => $value )
{
    putenv( "$key" );
}

但一切都不如我预期。任何新的见解都深表感谢。

编辑:

与此同时,我找到了一个替代的但并不令人满意的解决方案:当我使用 调用open -n -a Terminal ...(注意添加的-n参数)时exec(),它每次都会使用正确的环境变量启动一个新的终端会话。但这每次都会打开一个全新的终端实例。

更新:

通过测试环境变量,我使用一个脚本(而不是两个脚本)稍微简化了这个过程SHLVL。我还设法通过使用临时文件来消除新的终端实例,就像 Scott 建议的那样。结果如下。但我仍然觉得它不太优雅。

$shellLevel = getenv( 'SHLVL' );
if( 1 == $shellLevel )
{
    file_put_contents( 'env.dat', serialize( $_SERVER ) );
    exec( sprintf( 'open -a Terminal "%s" &', __FILE__ ) );
    exit( 0 );
}
else
{
    $_SERVER = unserialize( file_get_contents( 'env.dat' ) );
    unlink( 'env.dat' );
    foreach( $_SERVER as $key => $value )
    {
        putenv( "$key=$value" );
    }
}

因此,我仍然愿意听取其他建议(除了 Scott 已经提出的 CLI 和文件建议)。当然,除非这根本不可能(Scott 似乎已经暗示了这一点)。

答案1

您是否曾经使用过 Microsoft Office(Word、Excel 和 PowerPoint)?您是否曾经同时打开两个文档/工作簿/演示文稿?您是否注意到,您通常只会看到一个过程即使有两个窗口,您也无法使用相应的工具?很明显,终端中发生了类似的事情——第二个open请求由第一个请求创建的进程处理open,并且无需创建新进程即可处理。我猜是open检测到进程已经存在,然后向其发送一条消息,而不是创建新进程,或者诸如此类的事情。

至于您的另一个问题:环境变量从父进程传递到子进程(即,通过创建新进程时fork)并在调用之间保留exec。因此,它们沿着进程层次结构向下传递,直到进程退出或明确更改变量。因此,看起来第一个open导致终端的forkexec,从而传递环境,而第二个open只是向现有进程发送一条消息以再次运行../Unix Support/test.php,并且环境不会被传递。您似乎已经找到了使用环境实现所需功能的唯一方法——每次都强制创建新进程。其他方法将涉及通过其他方式传递所需数据,例如命令行或文件。

相关内容