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

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

setenv FOO foo

在csh/tcsh中,或

export FOO=foo

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

我已经知道了

source myscript

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

但问题是:

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

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

665056 次浏览
你将不能修改调用者的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`'
这样做的一个好处是,您只需要在一个地方维护列表。 从理论上讲,你甚至可以将列表放在一个文件中,并在“In”和“do”之间放置cat nvpairfilename

这基本上就是登录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环境。< / p > 请记住,您可以在bashprofile中使用功能,并且该函数将全局可用。 例如,“function user {export USER_NAME $1}”可以在运行时设置变量,例如:user olegchir &&Env | grep olegchir

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

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

如果在父shell (Perl或shell)退出后绝对有必要保留环境变量,则有必要由父shell来完成繁重的工作。我在文档中看到的一种方法是,当前脚本使用必要的“export”语言生成一个可执行文件,然后欺骗父shell执行它——如果您试图留下修改后环境的非易失性版本,那么始终要认识到这样一个事实,即您需要在命令前加上“source”。充其量是一个克鲁格。

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

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

通过使用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中运行并设置变量 正如预期和希望的那样。< / p >

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

所以为什么不简单地

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

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

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

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

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

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

. /path/to/set_env_vars.sh

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

. set_env_vars.sh

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

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

我很多年前就这么做了。如果我没记错的话,我在.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;

(如你所见,cshtcsh使用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中设置环境变量。

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