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

时间:2009-01-30 18:50:22

标签: bash shell csh tcsh

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

setenv FOO foo

在csh / tcsh或

export FOO=foo
sh / bash中的

仅在脚本执行期间设置它。

我已经知道了

source myscript

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

但这就是问题:

我希望这个脚本可以从bash或csh中调用。换句话说,我希望任何一个shell的用户都能够运行我的脚本并改变他们的shell环境。所以'source'对我来说不起作用,因为运行csh的用户无法获取bash脚本,而运行bash的用户无法获取csh脚本。

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

21 个答案:

答案 0 :(得分:277)

使用“点空间脚本”调用语法。例如,以下是使用脚本完整路径的方法:

. /path/to/set_env_vars.sh

如果你和脚本在同一个目录中,那么如何做到这一点:

. set_env_vars.sh

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

这与调用source set_env_vars.sh相同,但输入的时间较短,可能会在source没有的地方使用。

答案 1 :(得分:239)

您的shell进程拥有父级环境的副本,无法访问父进程的环境。当shell进程终止时,您对其环境所做的任何更改都将丢失。获取脚本文件是配置shell环境最常用的方法,您可能只想咬住子弹并为两种shell中的每一种维护一个。

答案 2 :(得分:53)

您无法修改调用方的shell,因为它位于不同的进程上下文中。当子进程继承shell的变量时,它们就是 继承自己的副本。

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

ln -s setit setit-sh

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新闻服务器中也有类似的功能。

答案 3 :(得分:44)

在我的.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
}

所以,当我想要禁用代理时, 函数在登录shell中运行并设置变量 如预期和通缉。

答案 4 :(得分:25)

通过使用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

答案 5 :(得分:12)

这有效 - 它不是我使用的,但它“有效”。让我们创建一个脚本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
%

退出此shell时,原始shell将接管:

% 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替换不存在的参数'。

答案 6 :(得分:10)

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

编辑:自2012年以来,模块包尚未更新,但仍然适用于基础知识。所有新功能,铃声和口哨都发生在今天的lmod中(我更喜欢它):https://www.tacc.utexas.edu/research-development/tacc-projects/lmod

答案 7 :(得分:8)

我没有提到的另一种解决方法是将变量值写入文件。

我遇到了一个非常类似的问题,我希望能够运行最后一次设置测试(而不是我所有的测试)。我的第一个计划是编写一个用于设置env变量TESTCASE的命令,然后使用另一个命令来运行测试。不用说我和你一样有同样的问题。

但后来我提出了这个简单的黑客攻击:

第一个命令(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

答案 8 :(得分:4)

在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).

答案 9 :(得分:3)

您可以指示子进程打印其环境变量(通过调用&#34; env&#34;),然后遍历父进程中的打印环境变量并调用&#34; export&#34;关于那些变量。

以下代码基于Capturing output of find . -print0 into a bash array

如果父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

答案 10 :(得分:2)

您可以使用不同的bash_profile调用另一个Bash。 此外,您可以创建特殊的bash_profile,以便在multi-bashprofile环境中使用。

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

答案 11 :(得分:1)

从技术上讲,这是正确的 - 只有'eval'不会分叉另一个shell。但是,从您尝试在修改后的环境中运行的应用程序的角度来看,差异是nil:子级继承其父级的环境,因此(修改的)环境将传递给所有降序进程。

事实上,改变后的环境变量“坚持” - 只要你在父程序/ shell下运行。

如果在父(Perl或shell)退出之后保留环境变量是绝对必要的,则父shell必须执行繁重的操作。我在文档中看到的一种方法是当前脚本使用必要的“导出”语言生成可执行文件,然后欺骗父shell执行它 - 始终认识到您需要前言的事实如果您试图将修改后的环境的非易失性版本留在后面,请使用'source'命令。最好是克鲁格。

第二种方法是修改启动shell环境的脚本(.bashrc或其他)以包含修改后的参数。这可能很危险 - 如果你填满初始化脚本,它可能会使你的shell在下次尝试启动时不可用。有很多工具可以修改当前的shell;通过在'启动器'上加上必要的调整,你也可以有效推动这些变化。 一般不是一个好主意;如果您只需要对特定应用程序套件进行环境更改,则必须返回并在之后将shell启动脚本返回到其原始状态(使用vi或其他任何内容)。

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

答案 12 :(得分:1)

简短的回答是否定的,您不能改变父进程的环境,但看起来您想要的是具有自定义环境变量和用户选择的shell的环境。

那么为什么不是简单的

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

然后,当您完成环境后,只需exit

答案 13 :(得分:1)

您可以随时使用别名

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

答案 14 :(得分:1)

另一种选择是使用&#34;环境模块&#34; (http://modules.sourceforge.net/)。不幸的是,这引入了第三种语言。您可以使用Tcl语言定义环境,但是有一些方便的命令可用于典型修改(prepend与append vs set)。您还需要安装环境模块。然后,您可以使用module load *XXX*命名所需的环境。模块命令基本上是Thomas Kammeyer所描述的eval机制的花哨别名。这里的主要优点是您可以用一种语言维护环境,并依赖于&#34;环境模块&#34;把它翻译成sh,ksh,bash,csh,tcsh,zsh,python(?!?!!)等。

答案 15 :(得分:1)

多年前我这样做了。如果我记得正确的话,我在每个.bashrc和.cshrc中都包含一个别名,带有参数,将设置环境的各种形式别名化为常用形式。

然后,您将在两个shell中的任何一个中获取的脚本都有一个具有该最后一个表单的命令,该命令在每个shell中都是合适的别名。

如果我找到具体的别名,我会发布它们。

答案 16 :(得分:1)

我使用管道,eval和信号创建了一个解决方案。

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

它可能适用于任何命令。

答案 17 :(得分:1)

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

#!/bin/bash
unset http_proxy

使文件可执行

sudo chmod 744 unsetvar

创建别名

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

只要您将包含脚本文件的文件夹附加到路径中,就可以使用它了。

答案 18 :(得分:0)

我没有看到任何答案记录如何通过合作流程解决此问题。使用ssh-agent之类的常见模式是让子进程打印父表格可以eval的表达式。

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;

(如您所见,cshtcsh使用setenv来设置变量。)

您自己的程序也可以这样做。

bash$ foo=$(makefoo)

你的makefoo脚本只会计算并打印该值,并让调用者随心所欲地执行它 - 将它分配给变量是一个常见的用例,但可能不是你想要的东西 - 代码到生成值的工具中。

答案 19 :(得分:0)

这不是我所说的出色的,但是如果您仍然需要从shell调用脚本,这也可以工作。这不是一个好的解决方案,但是对于单个静态环境变量,它就足够好了。

1。)创建一个退出条件为0(成功)或1(失败)的脚本

if [[ $foo == "True" ]]; then
    exit 0
else
    exit 1

2。)创建一个依赖于退出代码的别名。

alias='myscript.sh && export MyVariable'

您调用别名,该别名调用脚本,脚本评估条件,通过“ &&”退出零以在父外壳中设置环境变量是必需的。

这是漂浮物,但在紧要关头可能会有用。

答案 20 :(得分:-9)

除了写入条件,取决于$ SHELL / $ TERM设置为什么,没有。使用Perl有什么问题?它几乎无处不在(我想不出一个没有它的单一UNIX变体),它会让你省事。

相关问题