我试图编写一个shell脚本,当运行时,将设置一些环境变量,这些变量将在调用者的shell中保持设置。

setenv FOO foo

在csh/tcsh中,或

export FOO=foo

在sh/bash中只在脚本执行期间设置它。

我已经知道了

source myscript

将运行脚本的命令,而不是启动一个新的shell,这可能导致设置“调用者的”环境。

但问题是:

我希望这个脚本可以从bash或csh中调用。换句话说,我希望任何一个shell的用户都能够运行我的脚本,并更改他们的shell环境。因此,'source'对我来说不适用,因为运行csh的用户不能获取bash脚本的源代码,而运行bash的用户也不能获取csh脚本的源代码。

有没有合理的解决方案,不需要在脚本上编写和维护两个版本?


当前回答

您可以指示子进程打印它的环境变量(通过调用“env”),然后在父进程中循环打印的环境变量,并对这些变量调用“export”。

下面的代码基于find的捕获输出。-print0转换为bash数组

如果父shell是bash,则可以使用

while IFS= read -r -d $'\0' line; do
    export "$line"
done < <(bash -s <<< 'export VARNAME=something; env -0')
echo $VARNAME

如果父shell是破折号,则read不提供-d标志,代码将变得更加复杂

TMPDIR=$(mktemp -d)
mkfifo $TMPDIR/fifo
(bash -s << "EOF"
    export VARNAME=something
    while IFS= read -r -d $'\0' line; do
        echo $(printf '%q' "$line")
    done < <(env -0)
EOF
) > $TMPDIR/fifo &
while read -r line; do export "$(eval echo $line)"; done < $TMPDIR/fifo
rm -r $TMPDIR
echo $VARNAME

其他回答

除了根据$SHELL/$TERM设置的写入条件外,没有。使用Perl有什么问题?它非常普遍(我想不出有哪个UNIX变体没有它),而且它将为您省去这些麻烦。

Another option is to use "Environment Modules" (http://modules.sourceforge.net/). This unfortunately introduces a third language into the mix. You define the environment with the language of Tcl, but there are a few handy commands for typical modifications (prepend vs. append vs set). You will also need to have environment modules installed. You can then use module load *XXX* to name the environment you want. The module command is basically a fancy alias for the eval mechanism described above by Thomas Kammeyer. The main advantage here is that you can maintain the environment in one language and rely on "Environment Modules" to translate it to sh, ksh, bash, csh, tcsh, zsh, python (?!?!!), etc.

从技术上讲,这是正确的——只有'eval'不会派生另一个shell。然而,从您试图在修改后的环境中运行的应用程序的角度来看,差异为零:子进程继承其父进程的环境,因此(修改后的)环境被传递给所有下行进程。

事实上,只要你在父程序/shell下运行,更改的环境变量就会“保持不变”。

If it is absolutely necessary for the environment variable to remain after the parent (Perl or shell) has exited, it is necessary for the parent shell to do the heavy lifting. One method I've seen in the documentation is for the current script to spawn an executable file with the necessary 'export' language, and then trick the parent shell into executing it -- always being cognizant of the fact that you need to preface the command with 'source' if you're trying to leave a non-volatile version of the modified environment behind. A Kluge at best.

The second method is to modify the script that initiates the shell environment (.bashrc or whatever) to contain the modified parameter. This can be dangerous -- if you hose up the initialization script it may make your shell unavailable the next time it tries to launch. There are plenty of tools for modifying the current shell; by affixing the necessary tweaks to the 'launcher' you effectively push those changes forward as well. Generally not a good idea; if you only need the environment changes for a particular application suite, you'll have to go back and return the shell launch script to its pristine state (using vi or whatever) afterwards.

简而言之,没有好的(简单的)方法。想必这很难确保系统的安全性不会受到不可挽回的损害。

通过使用gdb和setenv(3)是“有点”可能的,尽管我很难推荐实际这样做。(另外,最近的ubuntu不会让你这样做,除非告诉内核对ptrace更加宽容,其他发行版也可能会这样)。

$ cat setfoo
#! /bin/bash

gdb /proc/${PPID}/exe ${PPID} <<END >/dev/null
call setenv("foo", "bar", 0)
END
$ echo $foo

$ ./setfoo
$ echo $foo
bar

这是有效的-这不是我要用的,但它“有效”。让我们创建一个脚本teredo来设置环境变量TEREDO_WORMS:

#!/bin/ksh
export TEREDO_WORMS=ukelele
exec $SHELL -i

它将由Korn shell解释,导出环境变量,然后用一个新的交互式shell替换自身。

在运行这个脚本之前,我们将环境中的SHELL设置为C SHELL,并且没有设置环境变量TEREDO_WORMS:

% env | grep SHELL
SHELL=/bin/csh
% env | grep TEREDO
%

当脚本运行时,你在一个新的shell中,另一个交互式C shell,但环境变量是设置的:

% teredo
% env | grep TEREDO
TEREDO_WORMS=ukelele
%

当你退出这个外壳时,原来的外壳接管:

% exit
% env | grep TEREDO
%

在原始shell的环境中没有设置环境变量。如果您使用exec teredo来运行该命令,那么原来的交互式shell将被设置环境的Korn shell所取代,然后依次被一个新的交互式C shell所取代:

% exec teredo
% env | grep TEREDO
TEREDO_WORMS=ukelele
%

如果您键入exit(或Control-D),那么您的shell将退出,可能会让您退出该窗口,或者将您带回到实验开始的前一层shell。

同样的机制也适用于Bash或Korn shell。您可能会发现退出命令之后的提示符出现在有趣的地方。


请注意评论中的讨论。这不是我推荐的解决方案,但它确实实现了单一脚本的目的,即设置与所有shell(接受-i选项以生成交互式shell)一起工作的环境。您还可以在选项后面添加“$@”以中继任何其他参数,这可能会使shell可用作通用的“设置环境和执行命令”工具。如果有其他参数,你可能想要省略-i,导致:

#!/bin/ksh
export TEREDO_WORMS=ukelele
exec $SHELL "${@-'-i'}"

“${@-'-i'}”位表示“如果参数列表包含至少一个参数,则使用原始参数列表;否则,用-i代替不存在的参数'。