shell脚本可以设置调用shell的环境variables吗?

我正在尝试编写一个shell脚本,在运行时将设置一些环境variables,这些variables将保留在调用者的shell中。

setenv FOO foo 

在csh / tcsh中,或者

 export FOO=foo 

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

我已经知道了

 source myscript 

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

但是这是一个难题:

我希望这个脚本可以从bash或者csh中调用。 换句话说,我希望任何一个shell的用户都能够运行我的脚本并改变shell的环境。 所以'源'不适合我,因为运行csh的用户不能获得bash脚本,而运行bash的用户不能获得csh脚本。

是否有任何合理的解决scheme,不涉及编写和维护脚本上的两个版本?

你的shell进程有一个父进程的副本,不能访问父进程的环境。 当你的shell进程终止你对其环境所做的任何更改时,都会丢失。 采购脚本文件是configurationshell环境最常用的方法,您可能只想咬一口子,并为shell的两种风格中的每一种维护一个。

使用“点空间脚本”调用语法。 例如,以下是如何使用脚本的完整path来完成此操作:

 . /path/to/set_env_vars.sh 

如果你和脚本位于同一个目录下,这里是如何做到的:

 . set_env_vars.sh 

这些脚本在当前shell下执行脚本,而不是加载另一个脚本(如果执行了./set_env_vars.sh会发生什么情况)。 因为它运行在同一个shell中,所设置的环境variables将在退出时可用。

这与调用source set_env_vars.sh是一样的,但是input的时间更短,并且可能在某些source不工作的地方工作。

你不能修改调用者的shell,因为它在不同的进程上下文中。 当subprocessinheritance你的shell的variables时,它们自己inheritance副本。

你可以做的一件事是编写一个脚本,发出正确的命令为tcsh或sh基于如何被调用。 如果你的脚本是“setit”那么做:

 ln -s setit setit-sh 

 ln -s setit setit-csh 

现在无论是直接还是别名,你都可以从sh来完成

 eval `setit-sh` 

或从这个csh

 eval `setit-csh` 

setit使用$ 0来确定它的输出样式。

这是人们用来获取TERM环境variables集的方式。

这里的好处是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 

用上面给出的符号链接和反引用expression式的评估,就得到了期望的结果。

为简化对csh,tcsh或类似shell的调用:

 alias dosetit 'eval `setit-csh`' 

或者sh,bash等等:

 alias dosetit='eval `setit-sh`' 

一件好事就是你只需要在一个地方维护这个列表。 理论上你甚至可以将这个列表粘贴到一个文件中,并将cat nvpairfilename放在“in”和“do”之间。

这几乎是如何完成loginshellterminal设置:脚本将输出要在loginshell中执行的语句。 通常会使用别名来简化调用,如“tset vt100”中所示。 正如在另一个答案中提到的那样,在INN UseNet新闻服务器中也有类似的function。

在我的.bash_profile中我有:

 # No Proxy function noproxy { /usr/local/sbin/noproxy #turn off proxy server unset http_proxy HTTP_PROXY https_proxy HTTPs_PROXY } # Proxy function setproxy { sh /usr/local/sbin/proxyon #turn on proxy server http_proxy=http://127.0.0.1:8118/ HTTP_PROXY=$http_proxy https_proxy=$http_proxy HTTPS_PROXY=$https_proxy export http_proxy https_proxy HTTP_PROXY HTTPS_PROXY } 

所以,当我想禁用代理,函数(S)运行在loginshell并设置variables按预期和想要的。

通过使用gdb和setenv(3)是可能的 ,尽pipe我很难推荐实际的做法。 (另外,也就是说,最近的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 

这工作 – 这不是我会用,但它'工作'。 让我们创build一个脚本teredo来设置环境variablesTEREDO_WORMS

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

它将被Korn shell解释,导出环境variables,然后用一个新的交互式shellreplace它自己。

在运行这个脚本之前,我们在环境中将SHELL设置为C shell,并且未设置环境variablesTEREDO_WORMS

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

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

 % teredo % env | grep TEREDO TEREDO_WORMS=ukelele % 

当你从这个shell中退出时,原来的shell会接pipe:

 % exit % env | grep TEREDO % 

环境variables没有在原来的shell环境中设置。 如果使用exec teredo运行该命令,那么原始的交互式shell会被设置为环境的Korn shell取代,然后又被一个新的交互式C shell取代:

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

如果你inputexit (或Control-D ),那么你的shell就会退出,可能会把你从窗口中注销,或者把你带回到实验开始的shell的上一级。

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


请注意评论中的讨论。 这不是一个我会推荐的解决scheme,但它确实达到了一个单一脚本的目的,即设置与所有shell一起工作的环境(接受-i选项来创build一个交互式shell)。 你也可以在选项之后加上"$@"来传递任何其他的参数,这样可以使得shell成为一个通用的“设置环境和执行命令”工具。 如果还有其他参数,您可能需要省略-i ,导致:

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

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

您应该使用模块,请参阅http://modules.sourceforge.net/

编辑:自2012年以来,模块包尚未更新,但仍基本工作正常。 所有新function,铃声和哨声发生在lmod这一天(我更喜欢它): https ://www.tacc.utexas.edu/research-development/tacc-projects/lmod

我没有看到提到的另一个解决方法是将variables值写入文件。

我遇到了一个非常类似的问题,我希望能够运行最后一组testing(而不是我所有的testing)。 我的第一个计划是编写一个命令来设置envvariablesTESTCASE,然后有另一个命令来运行testing。 不用说,我的问题和你一样。

但后来我想出了这个简单的黑客:

第一个命令( testset ):

 #!/bin/bash if [ $# -eq 1 ] then echo $1 > ~/.TESTCASE echo "TESTCASE has been set to: $1" else echo "Come again?" fi 

第二个命令( testrun ):

 #!/bin/bash TESTCASE=$(cat ~/.TESTCASE) drush test-run $TESTCASE 

在你的bash脚本的顶部添加-l标志即

 #!/usr/bin/env bash -l ... export NAME1="VALUE1" export NAME2="VALUE2" 

NAME1NAME2的值现在已被导出到当前环境,但这些更改不是永久性的。 如果你希望它们是永久的,你需要将它们添加到你的.bashrc文件或其他init文件中。

从手册页:

 -l Make bash act as if it had been invoked as a login shell (see INVOCATION below). 

您可以指示subprocess打印其环境variables(通过调用“env”),然后遍历父进程中打印的环境variables并调用这些variables的“导出”。

以下代码基于捕获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 

你可以使用不同的bash_profile调用另外一个Bash。 此外,您可以创build特殊的bash_profile以在多bashprofile环境中使用。

请记住,您可以使用bashprofile中的函数,并且该函数将在全局范围内可用。 例如,“function user {export USER_NAME $ 1}”可以在运行时设置variables,例如:user olegchir && env | grep olegchir

从技术上讲,这是正确的 – 只有“eval”不会分叉另一个shell。 但是,从应用程序的angular度来看,您试图在修改后的环境中运行,所不同的是,子项inheritance父项的环境,因此(修改的)环境会传递到所有递减的进程。

Ipso事实上,只要你在父程序/ shell下运行,改变了的环境variables“sticks”。

如果父节点(Perl或shell)退出后环境variables是绝对必要的,则父节点必须完成繁重的工作。 我在文档中看到的一种方法是当前脚本使用必要的“导出”语言产生一个可执行文件,然后欺骗父shell执行它 – 总是意识到事实上你需要命令与'源',如果你想离开一个非易失性版本的修改后的环境。 充其量是一个克鲁格。

第二种方法是修改启动shell环境的脚本(.bashrc或其他)来包含修改后的参数。 这可能是危险的 – 如果你初始化脚本,它可能会让你的shell下次尝试启动时不可用。 有很多修改当前shell的工具; 通过对“发射器”进行必要的调整,您也可以有效地推动这些变化。 一般不是一个好主意; 如果你只需要改变一个特定的应用程序套件的环境,那么你将不得不返回,然后将shell启动脚本返回到原始状态(使用vi或其他)。

总之,没有好的(而且容易的)方法。 据推测,这难以确保该系统的安全性不是不可挽回地受到损害。

简短的回答是否定的,你不能改变父进程的环境,但是你想要的是一个有自定义环境variables和用户select的shell的环境。

那么为什么不简单这样的事情

 #!/usr/bin/env bash FOO=foo $SHELL 

然后,当你完成了环境,就exit

你总是可以使用别名

 alias your_env='source ~/scripts/your_env.sh' 

另一种select是使用“环境模块”( http://modules.sourceforge.net/ )。 这不幸的是引入了第三种语言。 你用Tcl的语言来定义环境,但是对于典型的修改有一些方便的命令(prepend与append vs set)。 您还需要安装环境模块。 然后,您可以使用module load *XXX*命名您想要的环境。 模块命令基本上是Thomas Kammeyer上面描述的eval机制的别名。 这里的主要优点是你可以用一种语言来维护环境,并依靠“环境模块”将它转换为sh,ksh,bash,csh,tcsh,zsh,python(?!?!!)等。

我很多年前就这样做过。 如果我记得正确的话,我在每个.bashrc和.cshrc中join了一个别名,并带有参数,将环境设置为一种通用forms。

然后,您将在任何一个shell中源代码的脚本都会有一个最后一个forms的命令,这个命令在每个shell中都是合适的。

如果我find具体的别名,我会张贴他们。

我使用pipe道,eval和信号创build了一个解决scheme。

 parent() { if [ -z "$G_EVAL_FD" ]; then die 1 "Rode primeiro parent_setup no processo pai" fi if [ $(ppid) = "$$" ]; then "$@" else kill -SIGUSR1 $$ echo "$@">&$G_EVAL_FD fi } parent_setup() { G_EVAL_FD=99 tempfile=$(mktemp -u) mkfifo "$tempfile" eval "exec $G_EVAL_FD<>'$tempfile'" rm -f "$tempfile" trap "read CMD <&$G_EVAL_FD; eval \"\$CMD\"" USR1 } parent_setup #on parent shell context ( A=1 ); echo $A # prints nothing ( parent A=1 ); echo $A # prints 1 

它可能适用于任何命令。

在OS X bash下,您可以执行以下操作:
创buildbash脚本文件以取消设置variables

 #!/bin/bash unset http_proxy 

使文件可执行

 sudo chmod 744 unsetvar 

创build别名

 alias unsetvar='source /your/path/to/the/script/unsetvar' 

它应该准备好使用,只要你有包含脚本文件的文件夹附加到path。

除了写作条件取决于$ SHELL / $ TERM设置为,没有。 使用Perl有什么问题? 这是非常普遍的(我想不出一个没有它的单一的UNIX变体),它会省去你的麻烦。