我试图编写一个shell脚本,当运行时,将设置一些环境变量,这些变量将在调用者的shell中保持设置。
setenv FOO foo
在csh/tcsh中,或
export FOO=foo
在sh/bash中只在脚本执行期间设置它。
我已经知道了
source myscript
将运行脚本的命令,而不是启动一个新的shell,这可能导致设置“调用者的”环境。
但问题是:
我希望这个脚本可以从bash或csh中调用。换句话说,我希望任何一个shell的用户都能够运行我的脚本,并更改他们的shell环境。因此,'source'对我来说不适用,因为运行csh的用户不能获取bash脚本的源代码,而运行bash的用户也不能获取csh脚本的源代码。
有没有合理的解决方案,不需要在脚本上编写和维护两个版本?
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.
您将无法修改调用者的shell,因为它位于不同的进程上下文中。当子进程继承shell的变量时,它们会
继承副本本身。
您可以做的一件事是编写一个脚本,为tcsh发出正确的命令
或者基于调用方式的sh。如果你的脚本是“setit”,那么执行:
ln -s setit setit-sh
and
ln -s setit setit-csh
现在,您可以直接或在别名中从sh执行此操作
eval `setit-sh`
或者这个来自CSH
eval `setit-csh`
Setit使用$0来确定其输出样式。
这让人想起人们如何得到TERM环境变量集。
这里的优点是,setit只写在你喜欢的任何shell中,比如:
#!/bin/bash
arg0=$0
arg0=${arg0##*/}
for nv in \
NAME1=VALUE1 \
NAME2=VALUE2
do
if [ x$arg0 = xsetit-sh ]; then
echo 'export '$nv' ;'
elif [ x$arg0 = xsetit-csh ]; then
echo 'setenv '${nv%%=*}' '${nv##*=}' ;'
fi
done
使用上面给出的符号链接和反引号表达式的eval,可以得到预期的结果。
简化csh、tcsh或类似shell的调用:
alias dosetit 'eval `setit-csh`'
或者用于sh、bash等:
alias dosetit='eval `setit-sh`'
这样做的一个好处是,您只需要在一个地方维护列表。
理论上,你甚至可以把列表放在一个文件中,把cat nvpairfilename放在“In”和“do”之间。
这基本上就是登录shell终端设置的方式:脚本将输出要在登录shell中执行的语句。别名通常用于简化调用,如“tset vt100”。正如在另一个回答中提到的,INN UseNet新闻服务器中也有类似的功能。
从技术上讲,这是正确的——只有'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.
简而言之,没有好的(简单的)方法。想必这很难确保系统的安全性不会受到不可挽回的损害。
我没有看到任何关于如何使用协作流程来解决这个问题的说明。ssh-agent之类的常见模式是让子进程打印父进程可以求值的表达式。
bash$ eval $(shh-agent)
例如,ssh-agent可以选择Csh或与bourne兼容的输出语法。
bash$ ssh-agent
SSH2_AUTH_SOCK=/tmp/ssh-era/ssh2-10690-agent; export SSH2_AUTH_SOCK;
SSH2_AGENT_PID=10691; export SSH2_AGENT_PID;
echo Agent pid 10691;
(这会导致代理开始运行,但不允许您实际使用它,除非您现在将此输出复制粘贴到shell提示符中。)比较:
bash$ ssh-agent -c
setenv SSH2_AUTH_SOCK /tmp/ssh-era/ssh2-10751-agent;
setenv SSH2_AGENT_PID 10752;
echo Agent pid 10752;
(正如你所看到的,csh和tcsh使用setenv来设置变量。)
您自己的程序也可以做到这一点。
bash$ foo=$(makefoo)
makefoo脚本将简单地计算并打印值,并让调用者对其做任何想做的事情——将其分配给变量是一种常见的用例,但可能不希望将其硬编码到生成值的工具中。