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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/496702/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 17:55:10  来源:igfitidea点击:

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

bashshellcshtcsh

提问by Larry Gritz

I'm trying to write a shell script that, when run, will set some environment variables that will stay set in the caller's shell.

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

setenv FOO foo

in csh/tcsh, or

在 csh/tcsh 中,或

export FOO=foo

in sh/bash only set it during the script's execution.

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

I already know that

我已经知道了

source myscript

will run the commands of the script rather than launching a new shell, and that can result in setting the "caller's" environment.

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

But here's the rub:

但问题是:

I want this script to be callable from either bash or csh. In other words, I want users of either shell to be able to run my script and have their shell's environment changed. So 'source' won't work for me, since a user running csh can't source a bash script, and a user running bash can't source a csh script.

我希望这个脚本可以从 bash 或 csh 调用。换句话说,我希望任一 shell 的用户都能够运行我的脚本并更改他们的 shell 环境。所以“源”对我不起作用,因为运行 csh 的用户不能获取 bash 脚本,而运行 bash 的用户不能获取 csh 脚本。

Is there any reasonable solution that doesn't involve having to write and maintain TWO versions on the script?

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

采纳答案by converter42

Your shell process has a copy of the parent's environment and no access to the parent process's environment whatsoever. When your shell process terminates any changes you've made to its environment are lost. Sourcing a script file is the most commonly used method for configuring a shell environment, you may just want to bite the bullet and maintain one for each of the two flavors of shell.

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

回答by Humberto Romero

Use the "dot space script" calling syntax. For example, here's how to do it using the full path to a script:

使用“点空间脚本”调用语法。例如,以下是如何使用脚本的完整路径执行此操作:

. /path/to/set_env_vars.sh

And here's how to do it if you're in the same directory as the script:

如果您与脚本位于同一目录中,请执行以下操作:

. set_env_vars.sh

These execute the script under the current shell instead of loading another one (which is what would happen if you did ./set_env_vars.sh). Because it runs in the same shell, the environmental variables you set will be available when it exits.

它们在当前 shell 下执行脚本,而不是加载另一个脚本(如果你这样做了会发生什么./set_env_vars.sh)。因为它运行在同一个shell中,所以你设置的环境变量在它退出的时候就可以使用了。

This is the same thing as calling source set_env_vars.sh, but it's shorter to type and might work in some places where sourcedoesn't.

这与调用 相同source set_env_vars.sh,但键入更短,并且可能在某些source不需要的地方工作。

回答by Thomas Kammeyer

You're not going to be able to modify the caller's shell because it's in a different process context. When child processes inherit your shell's variables, they're inheriting copies themselves.

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

One thing you can do is to write a script that emits the correct commands for tcsh or sh based how it's invoked. If you're script is "setit" then do:

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

ln -s setit setit-sh

and

ln -s setit setit-csh

Now either directly or in an alias, you do this from sh

现在直接或在别名中,您可以从 sh 执行此操作

eval `setit-sh`

or this from csh

或者这个来自 csh

eval `setit-csh`

setit uses $0 to determine its output style.

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

This is reminescent of how people use to get the TERM environment variable set.

这让人想起人们如何使用 TERM 环境变量集。

The advantage here is that setit is just written in whichever shell you like as in:

这里的优点是 setit 只是用你喜欢的任何 shell 编写,如下所示:

#!/bin/bash
arg0=
alias dosetit 'eval `setit-csh`'
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

with the symbolic links given above, and the eval of the backquoted expression, this has the desired result.

使用上面给出的符号链接和反引号表达式的 eval,这具有所需的结果。

To simplify invocation for csh, tcsh, or similar shells:

要简化对 csh、tcsh 或类似 shell 的调用:

alias dosetit='eval `setit-sh`'

or for sh, bash, and the like:

或者对于 sh、bash 等:

# 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
}

One nice thing about this is that you only have to maintain the list in one place. In theory you could even stick the list in a file and put cat nvpairfilenamebetween "in" and "do".

这样做的一个好处是您只需要将列表保存在一个地方。从理论上讲,您甚至可以将列表粘贴到文件中并放在cat nvpairfilename“in”和“do”之间。

This is pretty much how login shell terminal settings used to be done: a script would output statments to be executed in the login shell. An alias would generally be used to make invocation simple, as in "tset vt100". As mentioned in another answer, there is also similar functionality in the INN UseNet news server.

这几乎是过去登录 shell 终端设置的方式:脚本将输出要在登录 shell 中执行的语句。别名通常用于使调用变得简单,如“tset vt100”。正如另一个答案中提到的,INN UseNet 新闻服务器中也有类似的功能。

回答by chris

In my .bash_profile I have :

在我的 .bash_profile 我有:

$ cat setfoo
#! /bin/bash

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

$ ./setfoo
$ echo $foo
bar

So when I want to disable the proxy, the function(s) run in the login shell and sets the variables as expected and wanted.

因此,当我想禁用代理时,函数在登录 shell 中运行并按预期和需要设置变量。

回答by Kjetil Joergensen

It's "kind of" possible through using gdb and setenv(3), although I have a hard time recommending actually doing this. (Additionally, i.e. the most recent ubuntu won't actually let you do this without telling the kernel to be more permissive about ptrace, and the same may go for other distros as well).

通过使用 gdb 和setenv(3)是“有点”可能的,尽管我很难推荐实际这样做。(此外,即最新的 ubuntu 实际上不会让您在不告诉内核对 ptrace 更加宽容的情况下执行此操作,其他发行版也可能如此)。

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

回答by Jonathan Leffler

This works — it isn't what I'd use, but it 'works'. Let's create a script teredoto set the environment variable TEREDO_WORMS:

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

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

It will be interpreted by the Korn shell, exports the environment variable, and then replaces itself with a new interactive shell.

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

Before running this script, we have SHELLset in the environment to the C shell, and the environment variable TEREDO_WORMSis not set:

在运行这个脚本之前,我们已经SHELL在环境中设置为C shell,TEREDO_WORMS没有设置环境变量:

% teredo
% env | grep TEREDO
TEREDO_WORMS=ukelele
%

When the script is run, you are in a new shell, another interactive C shell, but the environment variable is set:

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

% exit
% env | grep TEREDO
%

When you exit from this shell, the original shell takes over:

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

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

The environment variable is not set in the original shell's environment. If you use exec teredoto run the command, then the original interactive shell is replaced by the Korn shell that sets the environment, and then that in turn is replaced by a new interactive C shell:

未在原始 shell 的环境中设置环境变量。如果您使用exec teredo来运行命令,那么原始交互式 shell 将被设置环境的 Korn shell 替换,然后又被一个新的交互式 C shell 替换:

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

If you type exit(or Control-D), then your shell exits, probably logging you out of that window, or taking you back to the previous level of shell from where the experiments started.

如果您键入exit(或Control-D),则您的 shell 将退出,可能将您从该窗口中注销,或者将您带回从实验开始的上一层 shell。

The same mechanism works for Bash or Korn shell. You may find that the prompt after the exit commands appears in funny places.

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



Note the discussion in the comments. This is not a solution I would recommend, but it does achieve the stated purpose of a single script to set the environment that works with all shells (that accept the -ioption to make an interactive shell). You could also add "$@"after the option to relay any other arguments, which might then make the shell usable as a general 'set environment and execute command' tool. You might want to omit the -iif there are other arguments, leading to:

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

#!/bin/bash

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

The "${@-'-i'}"bit means 'if the argument list contains at least one argument, use the original argument list; otherwise, substitute -ifor the non-existent arguments'.

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

回答by Davide

You should use modules, see http://modules.sourceforge.net/

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

EDIT: The modules package has not been updated since 2012 but still works ok for the basics. All the new features, bells and whistles happen in lmod this day (which I like it more): https://www.tacc.utexas.edu/research-development/tacc-projects/lmod

编辑:模块包自 2012 年以来没有更新,但对于基础知识仍然可以正常工作。今天所有的新功能、花里胡哨都在 lmod 中发生(我更喜欢它):https://www.tacc.utexas.edu/research-development/tacc-projects/lmod

回答by dkinzer

Another workaround that I don't see mentioned is to write the variable value to a file.

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

I ran into a very similar issue where I wanted to be able to run the last set test (instead of all my tests). My first plan was to write one command for setting the env variable TESTCASE, and then have another command that would use this to run the test. Needless to say that I had the same exact issue as you did.

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

But then I came up with this simple hack:

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

First command ( testset):

第一个命令 ( testset):

#!/bin/bash

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

Second command (testrun):

第二个命令 ( testrun):

#!/usr/bin/env bash -l

...

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

回答by cristobal

Add the -l flag in top of your bash script i.e.

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

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

The values with NAME1and NAME2will now have been exported to your current environment, however these changes are not permanent. If you want them to be permanent you need to add them to your .bashrcfile or other init file.

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

From the man pages:

从手册页:

while IFS= read -r -d $'
TMPDIR=$(mktemp -d)
mkfifo $TMPDIR/fifo
(bash -s << "EOF"
    export VARNAME=something
    while IFS= read -r -d $'##代码##' 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
' line; do export "$line" done < <(bash -s <<< 'export VARNAME=something; env -0') echo $VARNAME

回答by klaus se

You can instruct the child process to print its environment variables (by calling "env"), then loop over the printed environment variables in the parent process and call "export" on those variables.

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

The following code is based on Capturing output of find . -print0 into a bash array

以下代码基于find 的捕获输出。-print0 到 bash 数组

If the parent shell is the bash, you can use

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

##代码##

If the parent shell is the dash, then readdoes not provide the -d flag and the code gets more complicated

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

##代码##