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

setenv FOO foo

在csh/tcsh中,或

export FOO=foo

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

我已经知道了

source myscript

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

但问题是:

我希望这个脚本可以从bash或csh中调用。换句话说,我希望任何一个shell的用户都能够运行我的脚本,并更改他们的shell环境。因此,'source'对我来说不适用,因为运行csh的用户不能获取bash脚本的源代码,而运行bash的用户也不能获取csh脚本的源代码。

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


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

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

ln -s setit setit-sh

and

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


您的shell进程拥有父进程环境的副本,并且不能访问父进程的任何环境。当您的shell进程终止时,您对其环境所做的任何更改都将丢失。获取脚本文件是配置shell环境最常用的方法,您可能只想咬紧牙关,为两种shell类型各维护一个脚本文件。


除了根据$SHELL/$TERM设置的写入条件外,没有。使用Perl有什么问题?它非常普遍(我想不出有哪个UNIX变体没有它),而且它将为您省去这些麻烦。


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

当你退出这个外壳时,原来的外壳接管:

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


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

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


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

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


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

事实上,只要你在父程序/shell下运行,更改的环境变量就会“保持不变”。

If it is absolutely necessary for the environment variable to remain after the parent (Perl or shell) has exited, it is necessary for the parent shell to do the heavy lifting. One method I've seen in the documentation is for the current script to spawn an executable file with the necessary 'export' language, and then trick the parent shell into executing it -- always being cognizant of the fact that you need to preface the command with 'source' if you're trying to leave a non-volatile version of the modified environment behind. A Kluge at best.

The second method is to modify the script that initiates the shell environment (.bashrc or whatever) to contain the modified parameter. This can be dangerous -- if you hose up the initialization script it may make your shell unavailable the next time it tries to launch. There are plenty of tools for modifying the current shell; by affixing the necessary tweaks to the 'launcher' you effectively push those changes forward as well. Generally not a good idea; if you only need the environment changes for a particular application suite, you'll have to go back and return the shell launch script to its pristine state (using vi or whatever) afterwards.

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


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

在我的.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中运行并设置变量 正如预期和希望的那样。


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

所以为什么不简单地

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

然后,当您完成环境时,只需退出。


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

我遇到了一个非常相似的问题,我希望能够运行最后一个集测试(而不是所有的测试)。我的第一个计划是编写一个命令来设置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

你可以使用别名

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

您可以指示子进程打印它的环境变量(通过调用“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

Another option is to use "Environment Modules" (http://modules.sourceforge.net/). This unfortunately introduces a third language into the mix. You define the environment with the language of Tcl, but there are a few handy commands for typical modifications (prepend vs. append vs set). You will also need to have environment modules installed. You can then use module load *XXX* to name the environment you want. The module command is basically a fancy alias for the eval mechanism described above by Thomas Kammeyer. The main advantage here is that you can maintain the environment in one language and rely on "Environment Modules" to translate it to sh, ksh, bash, csh, tcsh, zsh, python (?!?!!), etc.


使用“点空格脚本”调用语法。例如,下面是如何使用脚本的完整路径:

. /path/to/set_env_vars.sh

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

. set_env_vars.sh

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

这与调用源set_env_vars.sh是一样的事情,但它的输入更短,并且可能在源不能工作的某些地方工作。


我很多年前就这么做了。如果我没记错的话,我在.bashrc和.cshrc中都包含了一个别名,并带了参数,以别名形式将环境设置为公共形式。

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

如果我找到具体的化名,我会公布的。


我创建了一个解决方案使用管道,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

它可能适用于任何命令。


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

#!/bin/bash
unset http_proxy

使文件可执行

sudo chmod 744 unsetvar

创建别名

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

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


我没有看到任何关于如何使用协作流程来解决这个问题的说明。ssh-agent之类的常见模式是让子进程打印父进程可以求值的表达式。

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脚本将简单地计算并打印值,并让调用者对其做任何想做的事情——将其分配给变量是一种常见的用例,但可能不希望将其硬编码到生成值的工具中。


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

1)。创建一个条件为0(成功)或1(不成功)的脚本

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

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

alias='myscript.sh && export MyVariable'

调用别名,别名调用脚本,脚本计算条件,条件必须通过'&&'退出0,以便在父shell中设置环境变量。

这是垃圾,但在紧要关头可以派上用场。