506

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

setenv FOO foo

在 csh/tcsh 中,或

export FOO=foo

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

我已经知道了

source myscript

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

但问题是:

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

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

4

21 回答 21

508

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

. /path/to/set_env_vars.sh

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

. set_env_vars.sh

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

这与 call 相同,但它的输入时间更短,并且可能在某些不需要source set_env_vars.sh的地方工作。source

于 2015-02-12T23:04:57.163 回答
312

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

于 2009-01-30T19:06:58.643 回答
56

您将无法修改调用者的 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 新闻服务器中也有类似的功能。

于 2009-01-30T19:06:58.550 回答
50

在我的 .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 中运行并按预期设置变量。

于 2011-11-19T23:46:30.900 回答
35

通过使用 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
于 2011-07-08T22:03:34.663 回答
13

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

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

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

在运行这个脚本之前,我们已经在环境中设置了C shell,并且没有设置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不存在的参数'。

于 2009-01-30T22:17:05.827 回答
12

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

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

于 2009-02-04T18:52:14.927 回答
10

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

我遇到了一个非常相似的问题,我希望能够运行最后一组测试(而不是我的所有测试)。我的第一个计划是编写一个用于设置环境变量 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
于 2013-07-26T19:21:34.467 回答
3

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

#!/usr/bin/env bash -l

...

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

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

从手册页:

-l Make bash act as if it had been invoked as a login shell (see INVOCATION below).
于 2014-06-16T12:43:43.550 回答
3

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

以下代码基于捕获 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
于 2014-09-25T14:23:48.723 回答
3

这不是我所说的出色,但如果您无论如何都需要从 shell 调用脚本,这也有效。这不是一个好的解决方案,但是对于单个静态环境变量来说,它已经足够好了。

1.) 创建一个脚本,其条件退出 0(成功)或 1(不成功)

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

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

alias='myscript.sh && export MyVariable'

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

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

于 2018-08-31T15:50:33.523 回答
2

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

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

于 2010-10-29T06:15:02.647 回答
2

另一种选择是使用“环境模块”(http://modules.sourceforge.net/)。不幸的是,这将第三种语言引入了混合中。您使用 Tcl 语言定义环境,但有一些方便的命令可用于典型的修改(前置、附加和设置)。您还需要安装环境模块。然后,您可以使用module load *XXX*来命名您想要的环境。module 命令基本上是evalThomas Kammeyer 上述机制的一个奇特别名。这里的主要优点是您可以用一种语言维护环境并依靠“环境模块”将其翻译成 sh、ksh、bash、csh、tcsh、zsh、python(?!?!!)等。

于 2014-10-23T20:02:45.473 回答
2

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

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

它可能适用于任何命令。

于 2016-09-21T21:43:53.537 回答
2

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

#!/bin/bash
unset http_proxy

使文件可执行

sudo chmod 744 unsetvar

创建别名

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

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

于 2017-01-19T09:26:51.197 回答
2

我没有看到任何记录如何通过合作流程解决此问题的答案。一个常见的模式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;

(如您所见,csh用于tcsh设置setenv变量。)

您自己的程序也可以做到这一点。

bash$ foo=$(makefoo)

您的makefoo脚本将简单地计算并打印该值,并让调用者对它做任何他们想做的事情——将它分配给一个变量是一个常见的用例,但您可能不想硬编码到产生该值的工具中.

于 2017-11-23T09:25:36.253 回答
1

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

事实上,改变的环境变量'sticks'——只要你在父程序/shell下运行。

如果在父(Perl 或 shell)退出后环境变量绝对需要保留,则父 shell 有必要承担繁重的工作。我在文档中看到的一种方法是让当前脚本使用必要的“导出”语言生成一个可执行文件,然后诱使父 shell 执行它——始终意识到您需要在前面加上如果您试图留下修改后环境的非易失版本,则使用“source”命令。充其量是克鲁格。

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

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

于 2011-05-18T13:23:39.377 回答
1

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

那么为什么不简单地像

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

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

于 2013-02-28T06:41:39.053 回答
1

你总是可以使用别名

alias your_env='source ~/scripts/your_env.sh'
于 2014-04-10T23:14:57.643 回答
1

很多年前我就这样做了。如果我没记错的话,我在 .bashrc 和 .cshrc 中都包含了一个别名,带有参数,将环境设置为通用形式的相应形式别名。

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

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

于 2015-10-30T15:14:45.430 回答
-11

除了写作条件取决于 $SHELL/$TERM 的设置,没有。使用 Perl 有什么问题?它无处不在(我想不出一个没有它的 UNIX 变体),它会为您省去麻烦。

于 2009-01-30T19:08:12.200 回答