linux - shell教程 - unix shell
shell脚本可以设置调用shell的环境变量吗? (14)
从技术上讲,这是正确的 - 只有'eval'不会分叉另一个shell。 但是,从您尝试在修改的环境中运行的应用程序的角度来看,区别在于:子级继承其父级的环境,因此(修改的)环境会传递到所有递减的进程。
Ipso事实上,只要您在父程序/ shell下运行,更改后的环境变量“支持”即可。
如果父节点(Perl或shell)退出后环境变量仍然是必需的,则父节点必须完成繁重的工作。 我在文档中看到的一种方法是,当前脚本使用必要的“导出”语言生成可执行文件,然后欺骗父shell执行它 - 总是意识到需要为命令与'源',如果你想离开修改后的环境的非易失性版本。 最好的克鲁格。
第二种方法是修改启动shell环境的脚本(.bashrc或其他)以包含修改后的参数。 这可能是危险的 - 如果您初始化脚本的软管可能会让您的shell在下次尝试启动时不可用。 有很多修改当前shell的工具; 通过对“发射器”进行必要的调整,您也可以有效地推动这些变化。 一般来说不是一个好主意; 如果您只需要对特定应用程序套件进行环境更改,则必须返回,然后将shell启动脚本返回到其原始状态(使用vi或其他)。
总之,没有好的(和简单的)方法。 据推测,这很难确保系统的安全性不会受到不可挽回的损害。
我试图编写一个shell脚本,它在运行时会设置一些环境变量,这些变量将保持在调用者的shell中。
setenv FOO foo
在csh / tcsh中,或者
export FOO=foo
在sh / bash中只在脚本执行期间设置它。
我已经知道了
source myscript
将运行脚本的命令而不是启动一个新的shell,这可能导致设置“调用者”环境。
但是这是一个难题:
我希望这个脚本可以从bash或csh中调用。 换句话说,我希望任何一个shell的用户都能够运行我的脚本并改变shell的环境。 所以'源'不适合我,因为运行csh的用户无法获取bash脚本,而运行bash的用户无法获取csh脚本。
是否有任何合理的解决方案不涉及必须在脚本上编写和维护两个版本?
你不能修改调用者的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
用上面给出的符号链接和反引用表达式的评估,这具有期望的结果。
为了简化对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新闻服务器也有类似的功能。
使用“点空间脚本”调用语法。 例如,以下是如何使用脚本的完整路径进行操作的方法:
. /path/to/set_env_vars.sh
以下是如何与脚本位于同一目录中的方法:
. set_env_vars.sh
这些脚本在当前shell下执行脚本,而不是加载另一个脚本(如果执行./set_env_vars.sh
会发生这种情况)。 因为它运行在同一个shell中,所设置的环境变量将在退出时可用。
这与调用source set_env_vars.sh
是一样的,但是它的输入时间较短,并且可能在某些source
不可用的地方工作。
另一种选择是使用“环境模块”( http://modules.sourceforge.net/ )。 不幸的是,这种混合引入了第三种语言。 你用Tcl的语言来定义环境,但是对于典型的修改有一些方便的命令(prepend vs. append vs set)。 您还需要安装环境模块。 然后,您可以使用module load *XXX*
命名您想要的环境。 模块命令基本上是Thomas Kammeyer上面描述的eval
机制的一个奇特别名。 这里的主要优点是你可以用一种语言来维护环境,并依靠“环境模块”将它转换为sh,ksh,bash,csh,tcsh,zsh,python(?!?!!)等。
在你的bash脚本的顶部添加-l标志即
#!/usr/bin/env bash -l
...
export NAME1="VALUE1"
export NAME2="VALUE2"
NAME1
和NAME2
的值现在已被导出到当前环境,但这些更改不是永久性的。 如果你希望它们是永久的,你需要将它们添加到你的.bashrc
文件或其他init文件中。
从手册页:
-l Make bash act as if it had been invoked as a login shell (see INVOCATION below).
在我的.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中运行并根据预期设置变量。
您可以指示子进程打印其环境变量(通过调用“env”),然后遍历父进程中的打印环境变量并调用这些变量的“导出”。
以下代码基于捕获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/
编辑:自2012年以来,模块包尚未更新,但仍然适用于基础知识。 所有的新功能,铃声和哨声都发生在lmod这一天(我更喜欢它): https://www.tacc.utexas.edu/research-development/tacc-projects/lmod ://www.tacc.utexas.edu/research-development/tacc-projects/lmod
我使用管道,eval和信号创建了一个解决方案。
parent() {
if [ -z "$G_EVAL_FD" ]; then
die 1 "Rode primeiro parent_setup no processo pai"
fi
if [ $(ppid) = "$$" ]; then
"[email protected]"
else
kill -SIGUSR1 $$
echo "[email protected]">&$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
它可能适用于任何命令。
我很多年前就这样做过。 如果我记得正确的话,我在每个.bashrc和.cshrc中加入了一个别名,并带有参数,用于将环境设置为一种通用形式。
然后,您将在两个shell中的任何一个中源代码的脚本都会有一个带有最后一个形式的命令,这在每个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
最简单的答案是否定的,你不能改变父进程的环境,但它看起来像你想要的是一个具有自定义环境变量和用户选择的shell的环境。
那么,为什么不简单地这样
#!/usr/bin/env bash
FOO=foo $SHELL
然后当你完成环境时, exit
。
通过使用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
除了写作条件取决于$ SHELL / $ TERM设置为,no。 使用Perl有什么问题? 它非常无处不在(我想不出一个没有它的单一UNIX变体),它可以免除你的麻烦。