我试图编写一个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新闻服务器中也有类似的功能。

其他回答

通过使用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调用另一个Bash。 此外,您还可以创建特殊的bash_profile用于多bashprofile环境。

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

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

您将无法修改调用者的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新闻服务器中也有类似的功能。

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