我正在尝试编写一个shell脚本,在运行时,将设置一些环境变量,这些变量将保留在调用者的shell中 .
setenv FOO foo
在csh / tcsh中,或
export FOO=foo
在sh / bash中只在脚本执行期间设置它 .
我已经知道了
source myscript
将运行脚本的命令而不是启动新的shell,这可能导致设置“调用者”环境 .
但这是一个问题:
我希望这个脚本可以从bash或csh调用 . 换句话说,我希望任何一个shell的用户都能够运行我的脚本并改变他们的shell环境 . 所以'source'对我来说不起作用,因为运行csh的用户无法获取bash脚本,而运行bash的用户无法获取csh脚本 .
是否有任何合理的解决方案不涉及在脚本上编写和维护两个版本?
21 回答
除了写入条件取决于$ SHELL / $ TERM设置为什么,没有 . 使用Perl有什么问题?它几乎无处不在(我想不出没有它的单一UNIX变体),它会给你带来麻烦 .
通过使用gdb和setenv(3)可以实现"kind of",尽管我很难推荐实际执行此操作 . (此外,即最新的ubuntu实际上不会让你这样做而不告诉内核对ptrace更加宽容,同样可能也适用于其他发行版) .
这不是我称之为杰出的,但是如果你需要从shell调用脚本,这也有效 . 这不是一个好的解决方案,但对于单个静态环境变量,它运行良好 .
1.)创建一个条件退出0(成功)或1(不成功)的脚本
2.)创建一个依赖于退出代码的别名 .
您调用调用脚本的别名,该脚本评估条件,通过'&&'退出零以便在父shell中设置环境变量 .
这是漂浮物,但它可以在紧要关头使用 .
在我的.bash_profile中我有:
因此,当我想要禁用代理时,函数在登录shell中运行并按预期和需要设置变量 .
在bash脚本的顶部添加-l标志,即
现在,
NAME1
和NAME2
的值已导出到当前环境,但这些更改不是永久性的 . 如果您希望它们是永久性的,则需要将它们添加到.bashrc
文件或其他init文件中 .从手册页:
简短的回答是否定的,你不能改变父进程的环境,但看起来你想要的是一个具有自定义环境变量和用户选择的shell的环境 .
那么为什么不是简单的
然后,当您完成环境后,只需
exit
.使用“点空间脚本”调用语法 . 例如,以下是使用脚本完整路径的方法:
如果您与脚本位于同一目录中,请执行以下操作:
它们在当前shell下执行脚本而不是加载另一个脚本(如果你做了
./set_env_vars.sh
会发生这种情况) . 因为它在同一个shell中运行,所以您设置的环境变量在退出时将可用 .这与调用
source set_env_vars.sh
是一回事,但输入的时间更短,并且可能在某些source
没有的地方有用 .从技术上讲,这是正确的 - 只有'eval'不会分叉另一个shell . 但是,从您尝试在修改后的环境中运行的应用程序的角度来看,差异是nil:子级继承其父级的环境,因此(已修改的)环境将传递给所有降序进程 .
事实上,改变的环境变量'坚持' - 只要你在父程序/ shell下运行 .
如果在父(Perl或shell)退出之后保留环境变量是绝对必要的,则父shell必须执行繁重的操作 . 我在文档中看到的一种方法是当前脚本使用必要的“导出”语言生成可执行文件,然后欺骗父shell执行它 - 始终认识到你需要在前面加上如果您试图将修改后的环境的非易失性版本留在后面,请使用'source'命令 . 充其量只是一个克鲁格 .
第二种方法是修改启动shell环境的脚本(.bashrc或其他)以包含修改后的参数 . 这可能很危险 - 如果您填写初始化脚本,它可能会在下次尝试启动时使您的shell不可用 . 有很多工具可以修改当前的shell;通过在'启动器'上添加必要的调整,您可以有效地推动这些更改 . 一般不是一个好主意;如果你只需要对于特定应用程序套件的环境更改,您必须返回并在之后将shell启动脚本返回到其原始状态(使用vi或其他) .
简而言之,没有好的(简单的)方法 . 据推测,这很难确保系统的安全性不会受到不可挽回的损害 .
我使用管道,eval和信号创建了一个解决方案 .
它可能适用于任何命令 .
你总是可以使用别名
我很多年前就做过这个 . 如果我记得正确,我在每个.bashrc和.cshrc中都包含一个别名,带有参数,将设置环境的相应形式别名化为常用形式 .
然后,您将在两个shell中的任何一个中获取的脚本都有一个具有该最后一个窗体的命令,该命令在每个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新闻服务器中也有类似的功能 .
您可以指示子进程打印其环境变量(通过调用“env”),然后循环父进程中的打印环境变量并对这些变量调用“export” .
以下代码基于Capturing output of find . -print0 into a bash array
如果父shell是bash,则可以使用
如果父shell是破折号,则
read
不提供-d标志,代码变得更复杂你应该使用模块,参见http://modules.sourceforge.net/
编辑:自2012年以来,模块包尚未更新,但仍然适用于基础知识 . 所有新功能,铃声和口哨都发生在今天的lmod(我更喜欢它):https://www.tacc.utexas.edu/research-development/tacc-projects/lmod
您可以使用不同的bash_profile调用另一个Bash . 此外,您还可以创建特殊的bash_profile,以便在multi-bashprofile环境中使用 .
请记住,您可以使用bashprofile中的函数,并且该函数将在全局范围内可用 . 例如,"function user { export USER_NAME $1 }"可以在运行时设置变量,例如:user olegchir && env | grep olegchir
这有效 - 它不是't what I'使用,但它'works' . 让我们创建一个脚本
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可用作一般的'set environment and execute command'工具 . 如果有其他参数,您可能希望省略-i
,从而导致:"${@-'-i'}"
位表示'如果参数列表包含至少一个参数,则使用原始参数列表;否则,将-i
替换为不存在的参数' .在OS X bash下,您可以执行以下操作:
创建bash脚本文件以取消设置变量
使文件可执行
创建别名
只要您将包含脚本文件的文件夹附加到路径,就可以使用它 .
我没有看到任何答案记录如何通过合作流程解决这个问题 . 像
ssh-agent
这样的常见模式是让子进程打印一个父进程可以eval
的表达式 .例如,
ssh-agent
具有选择Csh或Bourne兼容输出语法的选项 .(这会导致代理开始运行,但不允许您实际使用它,除非您现在将此输出复制粘贴到shell提示符 . )比较:
(如您所见,
csh
和tcsh
使用setenv
来设置变量 . )你自己的程序也可以做到这一点 .
你的
makefoo
脚本只是简单地计算和打印它,并让调用者用它做任何他们想做的事情 - 将它分配给变量是一个常见的用例,但可能不是你想要硬编码到产生值 .您的shell进程具有父级环境的副本,并且无法访问父进程的环境 . 当shell进程终止时,您对其环境所做的任何更改都将丢失 . 获取脚本文件是配置shell环境最常用的方法,您可能只想咬住子弹并为两种shell中的每一种维护一个 .
另一种选择是使用"Environment Modules"(http://modules.sourceforge.net/) . 不幸的是,这引入了第三种语言 . 您可以使用Tcl语言定义环境,但是有一些方便的命令可用于典型修改(prepend与append vs set) . 您还需要安装环境模块 . 然后,您可以使用
module load *XXX*
命名所需的环境 . 模块命令基本上是Thomas Kammeyer所描述的eval
机制的奇特别名 . 这里的主要优点是你可以用一种语言维护环境,并依靠"Environment Modules"将它翻译成sh,ksh,bash,csh,tcsh,zsh,python(?!?!!)等 .我没有看到的另一个解决方法是将变量值写入文件 .
我遇到了一个非常类似的问题,我想能够运行最后一次设置测试(而不是我所有的测试) . 我的第一个计划是编写一个用于设置env变量TESTCASE的命令,然后使用另一个命令来运行测试 . 不用说我和你一样有同样的问题 .
但后来我想出了这个简单的黑客攻击:
第一个命令(
testset
):第二个命令(
testrun
):