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 脚本。
是否有任何合理的解决方案,不需要在脚本上编写和维护两个版本?
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.
setenv FOO foo
in csh/tcsh, or
export FOO=foo
in sh/bash only set it during the script's execution.
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.
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.
Is there any reasonable solution that doesn't involve having to write and maintain TWO versions on the script?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(20)
使用“点空间脚本”调用语法。 例如,以下是如何使用脚本的完整路径执行此操作:
如果您与脚本位于同一目录中,则以下是如何执行此操作:
这些在当前 shell 下执行脚本,而不是加载另一个脚本(即如果你这样做
./set_env_vars.sh
会发生什么)。 因为它运行在同一个shell中,所以你设置的环境变量在它退出时将可用。这与调用
source set_env_vars.sh
是一样的,但它的输入时间更短,并且可能在某些source
不起作用的地方起作用。Use the "dot space script" calling syntax. For example, here's how to do it using the full path to a script:
And here's how to do it if you're in the same directory as the script:
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.This is the same thing as calling
source set_env_vars.sh
, but it's shorter to type and might work in some places wheresource
doesn't.您的 shell 进程拥有父进程环境的副本,并且无法访问父进程的环境。 当 shell 进程终止时,您对其环境所做的任何更改都会丢失。 获取脚本文件是配置 shell 环境最常用的方法,您可能只是想硬着头皮为两种 shell 风格中的每一种维护一个脚本文件。
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 的变量时,它们是
继承副本本身。
您可以做的一件事是编写一个为 tcsh 发出正确命令的脚本
或 sh 基于它的调用方式。 如果您的脚本是“setit”,则执行:
现在
直接或以别名形式,您可以从 sh 执行此操作
,或从 csh 执行此操作
setit 使用 $0 来确定其输出样式。
这让人想起人们如何获取 TERM 环境变量集。
这里的优点是 setit 可以用您喜欢的任何 shell 编写,如下所示:
使用上面给出的符号链接以及反引号表达式的 eval,这将获得所需的结果。
简化对 csh、tcsh 或类似 shell 的调用:
或对 sh、bash 等的调用:
这样做的一个好处是您只需在一处维护该列表。
理论上,您甚至可以将列表粘贴到文件中,并将
cat nvpairfilename
放在“in”和“do”之间。这几乎就是登录 shell 终端设置的过去方式:脚本将输出要在登录 shell 中执行的语句。 别名通常用于使调用变得简单,如“tset vt100”。 正如另一个答案中提到的,INN UseNet 新闻服务器中也有类似的功能。
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.
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:
and
Now either directly or in an alias, you do this from sh
or this from csh
setit uses $0 to determine its output style.
This is reminescent of how people use to get the TERM environment variable set.
The advantage here is that setit is just written in whichever shell you like as in:
with the symbolic links given above, and the eval of the backquoted expression, this has the desired result.
To simplify invocation for csh, tcsh, or similar shells:
or for sh, bash, and the like:
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 nvpairfilename
between "in" and "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.
在我的 .bash_profile 中,我有:
所以当我想禁用代理时,
函数在登录 shell 中运行并设置变量
正如预期和想要的那样。
In my .bash_profile I have :
So when I want to disable the proxy,
the function(s) run in the login shell and sets the variables
as expected and wanted.
通过使用 gdb 和 setenv(3) “有点”可能,尽管我很难建议实际这样做这。 (此外,即最新的 ubuntu 实际上不会让您在不告诉内核对 ptrace 更加宽容的情况下执行此操作,其他发行版也可能如此)。
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).
这有效——这不是我会用的,但它“有效”。 让我们创建一个脚本
teredo
来设置环境变量TEREDO_WORMS
:它将由 Korn shell 解释,导出环境变量,然后用新的交互式 shell 替换自身。
在运行此脚本之前,我们已将环境中的
SHELL
设置为 C shell,并且未设置环境变量TEREDO_WORMS
:当脚本运行时,您处于新 shell,另一个交互式 C shell,但设置了环境变量:
当您退出此 shell 时,原始 shell 接管:
原始 shell 的环境中未设置环境变量。 如果您使用 exec teredo 运行该命令,则原始交互式 shell 会被设置环境的 Korn shell 替换,然后又会被新的交互式 C shell 替换:
如果您键入
exit
(或 Control-D),然后您的 shell 退出,可能会将您从该窗口注销,或者带您返回到实验开始处的上一个 shell 级别。相同的机制适用于 Bash 或 Korn shell。 您可能会发现退出命令后的提示出现在有趣的地方。
请注意评论中的讨论。 这不是我推荐的解决方案,但它确实实现了单个脚本的既定目的,以设置适用于所有 shell 的环境(接受
-i
选项来创建交互式 shell)。 您还可以在选项后添加"$@"
来转发任何其他参数,这可能会使 shell 用作通用的“设置环境和执行命令”工具。 如果还有其他参数,您可能需要省略-i
,从而导致:"${@-'-i'}"
位表示“如果参数列表”包含至少一个参数,使用原始参数列表; 否则,用-i
替换不存在的参数'。This works — it isn't what I'd use, but it 'works'. Let's create a script
teredo
to set the environment variableTEREDO_WORMS
:It will be interpreted by the Korn shell, exports the environment variable, and then replaces itself with a new interactive shell.
Before running this script, we have
SHELL
set in the environment to the C shell, and the environment variableTEREDO_WORMS
is not set:When the script is run, you are in a new shell, another interactive C shell, but the environment variable is set:
When you exit from this shell, the original shell takes over:
The environment variable is not set in the original shell's environment. If you use
exec teredo
to 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: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.The same mechanism works for Bash or Korn shell. You may find that the prompt after the exit commands appears in funny places.
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
-i
option 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-i
if there are other arguments, leading to:The
"${@-'-i'}"
bit means 'if the argument list contains at least one argument, use the original argument list; otherwise, substitute-i
for the non-existent arguments'.您应该使用模块,请参阅 http://modules.sourceforge.net/
编辑:模块包尚未自 2012 年以来更新,但基本功能仍然可以正常工作。 所有的新功能、花哨的功能都发生在 lmod 今天(我更喜欢它): https://www.tacc.utexas.edu/research-development/tacc-projects/lmod
You should use modules, see 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
我没有看到提到的另一个解决方法是将变量值写入文件。
我遇到了一个非常类似的问题,我希望能够运行最后一组测试(而不是所有测试)。 我的第一个计划是编写一个命令来设置环境变量 TESTCASE,然后使用另一个命令来运行测试。 不用说,我和你有同样的问题。
但后来我想出了这个简单的技巧:
第一个命令(
testset
):第二个命令(
testrun
):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.
But then I came up with this simple hack:
First command (
testset
):Second command (
testrun
):您可以指示子进程打印其环境变量(通过调用“env”),然后循环父进程中打印的环境变量并对这些变量调用“export”。
以下代码基于 捕获 find 的输出。 -print0 到 bash 数组
如果父 shell 是 bash,则可以使用
\0' line; do export "$line" done < <(bash -s <<< 'export VARNAME=something; env -0') echo $VARNAME如果父 shell 是破折号,则
read
不提供 -d 标志,代码会变得更多复杂的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.
The following code is based on Capturing output of find . -print0 into a bash array
If the parent shell is the bash, you can use
\0' line; do export "$line" done < <(bash -s <<< 'export VARNAME=something; env -0') echo $VARNAMEIf the parent shell is the dash, then
read
does not provide the -d flag and the code gets more complicated在 OS X bash 下,您可以执行以下操作:
创建 bash 脚本文件以取消设置变量
使文件可执行
创建别名
只要您将包含脚本文件的文件夹附加到路径中,它就应该可以使用了。
Under OS X bash you can do the following:
Create the bash script file to unset the variable
Make the file executable
Create alias
It should be ready to use so long you have the folder containing your script file appended to the path.
您可以使用不同的 bash_profile 调用另一个 Bash。
此外,您可以创建特殊的 bash_profile 以在多 bashprofile 环境中使用。
请记住,您可以在 bashprofile 中使用函数,并且该函数将在全局范围内可用。
例如,“function user {export USER_NAME $1 }”可以在运行时设置变量,例如:user olegchir && 环境| 格列普·奥莱格希尔
You can invoke another one Bash with the different bash_profile.
Also, you can create special bash_profile for using in multi-bashprofile environment.
Remember that you can use functions inside of bashprofile, and that functions will be avialable globally.
for example, "function user { export USER_NAME $1 }" can set variable in runtime, for example: user olegchir && env | grep olegchir
另一种选择是使用“环境模块”(http://modules.sourceforge.net/)。 不幸的是,这引入了第三种语言。 您可以使用 Tcl 语言定义环境,但有一些方便的命令可用于典型修改(前置、附加、设置)。 您还需要安装环境模块。 然后,您可以使用
module load *XXX*
来命名您想要的环境。 module 命令基本上是 Thomas Kammeyer 上面描述的 eval 机制的一个奇特别名。 这里的主要优点是你可以用一种语言维护环境,并依靠“环境模块”将其翻译为 sh、ksh、bash、csh、tcsh、zsh、python(?!?!!)等。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 theeval
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.我使用管道、eval 和信号创建了一个解决方案。
它可能适用于任何命令。
I created a solution using pipes, eval and signal.
It might work with any command.
我没有看到任何答案记录如何通过合作流程解决此问题。
ssh-agent
之类的常见模式是让子进程打印父进程可以eval
的表达式。例如,
ssh-agent
具有选择 Csh 或 Bourne 兼容输出语法的选项。(这会导致代理开始运行,但不允许您实际使用它,除非您现在将此输出复制粘贴到 shell 提示符中。)比较:(
如您所见,
csh
并且tcsh
使用setenv
设置变量。)您自己的程序也可以执行此操作。
你的 makefoo 脚本将简单地计算并打印该值,并让调用者用它做任何他们想做的事情——将它分配给变量是一个常见的用例,但可能不是你想要硬做的事情 -将代码写入产生值的工具中。
I don't see any answer documenting how to work around this problem with cooperating processes. A common pattern with things like
ssh-agent
is to have the child process print an expression which the parent caneval
.For example,
ssh-agent
has options to select Csh or Bourne-compatible output syntax.(This causes the agent to start running, but doesn't allow you to actually use it, unless you now copy-paste this output to your shell prompt.) Compare:
(As you can see,
csh
andtcsh
usessetenv
to set varibles.)Your own program can do this, too.
Your
makefoo
script would simply calculate and print the value, and let the caller do whatever they want with it -- assigning it to a variable is a common use case, but probably not something you want to hard-code into the tool which produces the value.这不是我所说的杰出,但如果您无论如何都需要从 shell 调用脚本,这也可以工作。 这不是一个好的解决方案,但对于单个静态环境变量来说,它已经足够好了。
1.) 创建一个脚本,其退出条件为 0(成功)或 1(不成功)
2.) 创建一个依赖于退出代码的别名。
您调用别名,该别名调用脚本,该脚本评估条件,需要通过“&&”退出零 为了在父 shell 中设置环境变量。
这是废物,但在紧要关头还是有用的。
It's not what I would call outstanding, but this also works if you need to call the script from the shell anyway. It's not a good solution, but for a single static environment variable, it works well enough.
1.) Create a script with a condition that exits either 0 (Successful) or 1 (Not successful)
2.) Create an alias that is dependent on the exit code.
You call the alias, which calls the script, which evaluates the condition, which is required to exit zero via the '&&' in order to set the environment variable in the parent shell.
This is flotsam, but it can be useful in a pinch.
从技术上讲,这是正确的——只有“eval”不会派生另一个 shell。 但是,从您尝试在修改后的环境中运行的应用程序的角度来看,差异为零:子级继承其父级的环境,因此(修改后的)环境将传递给所有下行进程。
事实上,只要您在父程序/shell 下运行,更改后的环境变量就会“保留”。
如果在父级(Perl 或 shell)退出后环境变量绝对有必要保留,则父级 shell 必须承担繁重的工作。 我在文档中看到的一种方法是让当前脚本使用必要的“导出”语言生成一个可执行文件,然后欺骗父 shell 执行它——始终认识到您需要在如果您试图保留已修改环境的非易失性版本,请使用带有“source”的命令。 充其量是克鲁格。
第二种方法是修改启动 shell 环境(.bashrc 或其他)的脚本以包含修改后的参数。 这可能很危险——如果您使用初始化脚本,它可能会使您的 shell 下次尝试启动时不可用。 有很多工具可以修改当前的 shell; 通过对“启动器”进行必要的调整,您也可以有效地推动这些更改。
一般来说这不是一个好主意; 如果您只需要更改特定应用程序套件的环境,则之后必须返回并将 shell 启动脚本返回到其原始状态(使用 vi 或其他方式)。
简而言之,没有好的(且简单的)方法。 据推测,这是为了确保系统的安全性不会受到不可挽回的损害而变得困难。
Technically, that is correct -- only 'eval' doesn't fork another shell. However, from the point of view of the application you're trying to run in the modified environment, the difference is nil: the child inherits the environment of its parent, so the (modified) environment is conveyed to all descending processes.
Ipso facto, the changed environment variable 'sticks' -- as long as you are running under the parent program/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.
In short, there are no good (and easy) methods. Presumably this was made difficult to ensure the security of the system was not irrevocably compromised.
简短的答案是否定的,您无法更改父进程的环境,但似乎您想要的是一个具有自定义环境变量和用户选择的 shell 的环境。
那么,为什么不简单地执行类似
“Then”这样的操作,当您完成环境后,只需
exit
即可。The short answer is no, you cannot alter the environment of the parent process, but it seems like what you want is an environment with custom environment variables and the shell that the user has chosen.
So why not simply something like
Then when you are done with the environment, just
exit
.您始终可以使用别名
You could always use aliases
我很多年前就这么做过。 如果我没记错的话,我在 .bashrc 和 .cshrc 中都包含了一个带有参数的别名,将设置环境的相应形式别名为通用形式。
然后,您将在两个 shell 中的任何一个中获取的脚本都有一个采用最后一种形式的命令,该命令在每个 shell 中都有合适的别名。
如果我找到具体的别名,我会发布它们。
I did this many years ago. If I rememeber correctly, I included an alias in each of .bashrc and .cshrc, with parameters, aliasing the respective forms of setting the environment to a common form.
Then the script that you will source in any of the two shells has a command with that last form, that is suitable aliased in each shell.
If I find the concrete aliases, I will post them.
除了取决于 $SHELL/$TERM 设置的写入条件之外,没有。 使用 Perl 有什么问题? 它非常普遍(我想不出哪个 UNIX 变体没有它),而且它会帮你省去麻烦。
Other than writings conditionals depending on what $SHELL/$TERM is set to, no. What's wrong with using Perl? It's pretty ubiquitous (I can't think of a single UNIX variant that doesn't have it), and it'll spare you the trouble.