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

Can a shell script set environment variables of the calling shell?

本问题已经有最佳答案,请猛点这里访问。

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

1
setenv FOO foo

在CSH/TCSH中,或

1
export FOO=foo

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

我已经知道了

1
source myscript

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

但问题是:

我希望这个脚本可以从bash或csh调用。换句话说,我希望任何一个shell的用户都能够运行我的脚本并改变shell的环境。所以"source"对我不起作用,因为运行csh的用户不能源代码bash脚本,而运行bash的用户不能源代码csh脚本。

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


使用"点空间脚本"调用语法。例如,下面介绍如何使用脚本的完整路径执行此操作:

1
. /path/to/set_env_vars.sh

如果您和脚本在同一个目录中,下面是如何执行此操作的方法:

1
. set_env_vars.sh

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

这与调用source set_env_vars.sh是一样的,但它的类型较短,并且可能在source没有的某些地方工作。


您的shell进程有一个父进程环境的副本,无法访问父进程的环境。当shell进程终止时,您对其环境所做的任何更改都将丢失。源代码脚本文件是配置shell环境最常用的方法,您可能只想咬紧牙关,为两种shell风格中的每一种维护一个。


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

您可以做的一件事是编写一个为tcsh发出正确命令的脚本或者基于sh的调用方式。如果脚本是"setit",则执行以下操作:

1
ln -s setit setit-sh

1
ln -s setit setit-csh

现在,无论是直接还是在别名中,您都可以从sh执行此操作。

1
eval `setit-sh`

或者CSH的这个

1
eval `setit-csh`

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

这让人想起了人们如何使用"环境变量集"这个术语。

这里的优势在于,setit只是用您喜欢的shell编写的,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/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的调用,请执行以下操作:

1
alias dosetit 'eval `setit-csh`'

或者对于sh、bash等:

1
alias dosetit='eval `setit-sh`'

这方面的一个好处是,您只需要在一个地方维护列表。理论上,你甚至可以把清单放在一个文件中,把cat nvpairfilename放在"in"和"do"之间。

这是登录shell终端设置过去的基本方法:脚本将输出要在登录shell中执行的语句。别名通常用于简化调用,如"tset vt100"。正如另一个答案中提到的,Inn-Usenet新闻服务器中也有类似的功能。


在我的.bash_简介中,我有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 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中运行并设置变量如预期和所需。


通过使用gdb和setenv(3),这是"一种"可能的,尽管我很难推荐实际这么做。(此外,也就是说,最近的Ubuntu实际上不会让您在不告诉内核对ptrace更宽容的情况下这样做,其他发行版也可能会这样做)。

1
2
3
4
5
6
7
8
9
10
11
$ 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

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

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

在运行此脚本之前,我们已经将环境中的SHELL设置为c shell,并且未设置环境变量TEREDO_WORMS

1
2
3
4
% env | grep SHELL
SHELL=/bin/csh
% env | grep TEREDO
%

运行脚本时,您将处于另一个交互式C shell的新shell中,但环境变量已设置:

1
2
3
4
% teredo
% env | grep TEREDO
TEREDO_WORMS=ukelele
%

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

1
2
3
% exit
% env | grep TEREDO
%

环境变量未在原始shell的环境中设置。如果使用exec teredo运行该命令,那么原始的交互式shell将被设置环境的korn shell替换,然后由新的交互式c shell替换:

1
2
3
4
% exec teredo
% env | grep TEREDO
TEREDO_WORMS=ukelele
%

如果您键入exit(或control dakbd),那么您的shell将退出,可能将您从该窗口注销,或者将您带回到实验开始的shell的前一级。

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

注意评论中的讨论。这不是我推荐的解决方案,但它确实实现了单个脚本的指定目的,以设置与所有shell一起工作的环境(接受-i选项以生成交互式shell)。您还可以在选项后添加"$@",以中继任何其他参数,这样可以使shell可用作一般的"设置环境和执行命令"工具。如果存在其他论点,您可能希望省略-i,从而导致:

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

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


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

编辑:模块包自2012年以来没有更新,但基本功能仍然正常。所有新的功能、铃声和口哨今天都在lmod中出现(我更喜欢它):https://www.tacc.utexas.edu/research-development/tacc-projects/lmod


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

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

但后来我想出了一个简单的方法:

第一命令(testset):

1
2
3
4
5
6
7
8
9
#!/bin/bash

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

第二命令(testrun):

1
2
3
4
#!/bin/bash

TESTCASE=$(cat ~/.TESTCASE)
drush test-run $TESTCASE

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

1
2
3
4
5
6
#!/usr/bin/env bash -l

...

export NAME1="VALUE1"
export NAME2="VALUE2"

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

从手册页:

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


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

以下代码基于捕获find的输出。-将0打印到bash数组中

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
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_配置文件调用另一个单bash。此外,还可以创建用于多bash profile环境的特殊bash_概要文件。

请记住,您可以在bashprofile中使用函数,并且这些函数可以全局访问。例如,"函数用户导出用户名$1"可以在运行时设置变量,例如:用户olegchir&;&;env grep olegchir


从技术上讲,这是正确的——只有"eval"才不会分叉。但是,从您试图在修改后的环境中运行的应用程序的角度来看,差异为零:子级继承其父级的环境,因此(修改后的)环境被传递到所有降序进程。

事实上,只要您在父程序/shell下运行,更改后的环境变量"stacks"。

如果环境变量在父级(perl或shell)退出后绝对需要保留,则父级shell必须执行重载提升。我在文档中看到的一种方法是,当前脚本使用必要的"export"语言生成一个可执行文件,然后诱骗父shell执行它——始终要知道,如果您试图保留修改后的环境的非易失性版本,则需要在命令前面加上"source"。在后面。充其量不过是一个蹩脚的废话。

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

简而言之,没有好的(和容易的)方法。很可能这很难确保系统的安全性不会受到不可逆转的损害。


你可以一直使用别名

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

另一个选项是使用"环境模块"(http://modules.sourceforge.net/)。不幸的是,这在混合语言中引入了第三种语言。您使用tcl语言定义环境,但对于典型的修改(prepend与append与set),有一些方便的命令。您还需要安装环境模块。然后,您可以使用module load *XXX*来命名所需的环境。module命令基本上是上面由ThomasKammeyer描述的eval机制的一个奇特别名。这里的主要优点是,您可以用一种语言维护环境,并依靠"环境模块"将其转换为sh、ksh、bash、csh、tcsh、zsh、python(????!!!)等。


我很多年前就做过这个。如果我记错了,我在.bashrc和.cshrc中的每一个中都包含了一个别名,带有参数,为将环境设置为公共形式的各个形式添加了别名。

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

如果我找到具体的别名,我会把它们贴出来。


我用管道、评估和信号创建了一个解决方案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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下,可以执行以下操作:< BR>创建bash脚本文件以取消设置变量

1
2
#!/bin/bash
unset http_proxy

使文件可执行

1
sudo chmod 744 unsetvar

创建别名

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

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


简短的回答是"否",您不能更改父进程的环境,但您想要的似乎是一个具有自定义环境变量和用户选择的shell的环境。

为什么不简单地说

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

然后,当您处理完环境之后,只需exit


这不是我所说的出色,但如果您无论如何都需要从shell调用脚本,这也可以工作。这不是一个好的解决方案,但是对于一个静态环境变量来说,它工作得很好。

1.)使用退出0(成功)或1(不成功)的条件创建脚本

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

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

1
alias='myscript.sh && export MyVariable'

调用别名,该别名调用脚本,该脚本计算条件,需要通过"&;&;"退出零,以便在父shell中设置环境变量。

这是漂浮物,但在紧急情况下可能有用。


我看不到任何关于如何通过协作过程来解决这个问题的答案。与ssh-agent类似的一个常见模式是让子进程打印一个父进程可以eval的表达式。

1
bash$ eval $(shh-agent)

例如,ssh-agent可以选择csh或bourne兼容的输出语法。

1
2
3
4
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提示。)比较:

1
2
3
4
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设置变量。)

你自己的程序也能做到这一点。

1
bash$ foo=$(makefoo)

您的makefoo脚本只需计算和打印值,并让调用者使用它做他们想做的任何事情——将它分配给一个变量是一个常见的用例,但可能不是您想要硬编码到产生值的工具中的东西。


除了根据$shell/$term的设置编写条件外,没有。使用Perl有什么问题?它是非常普遍的(我想不出一个没有它的Unix变体),而且它可以省去你的麻烦。