【问题标题】:Can a shell script set environment variables of the calling shell? [duplicate]shell脚本可以设置调用shell的环境变量吗? [复制]
【发布时间】:2010-10-04 13:06:28
【问题描述】:

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

setenv FOO foo

在 csh/tcsh 中,或

export FOO=foo

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

我已经知道了

source myscript

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

但问题来了:

我希望这个脚本可以从 bash 或 csh 调用。换句话说,我希望任一 shell 的用户都能够运行我的脚本并改变他们的 shell 环境。所以“source”对我不起作用,因为运行 csh 的用户无法获取 bash 脚本,而运行 bash 的用户无法获取 csh 脚本。

是否有任何合理的解决方案,无需在脚本上编写和维护两个版本?

【问题讨论】:

  • @eusoubrasileiro 不起作用(至少在 osx 上),因为 bash 将“export”解释为文件名。
  • 在此线程中查看@Humberto Romero 的回答stackoverflow.com/a/28489593/881375
  • 这个Q的标题应该改一下——主要区别是使用两个不同的shell,标题没有体现出来。
  • 在此处为 Linux 和 Windows 提供答案:unix.stackexchange.com/questions/38205/…

标签: bash shell csh tcsh


【解决方案1】:

这不是我所说的出色,但是如果您无论如何都需要从 shell 调用脚本,这也可以。这不是一个好的解决方案,但对于单个静态环境变量来说,它已经足够好了。

1.) 创建一个退出条件为 0(成功)或 1(不成功)的脚本

if [[ $foo == "True" ]]; then
    exit 0
else
    exit 1

2.) 创建一个依赖于退出代码的别名。

alias='myscript.sh && export MyVariable'

您调用别名,该别名调用脚本,该脚本评估条件,需要通过“&&”退出零,以便在父 shell 中设置环境变量。

这是漂浮物,但在紧要关头它可以派上用场。

【讨论】:

    【解决方案2】:

    我没有看到任何记录如何通过合作流程解决此问题的答案。像ssh-agent 这样的常见模式是让子进程打印一个父进程可以eval 的表达式。

    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 脚本将简单地计算并打印该值,然后让调用者对它做任何他们想做的事情——将它分配给一个变量是一个常见的用例,但可能不是您想要硬编码的东西产生价值的工具。

    【讨论】:

      【解决方案3】:

      使用“点空间脚本”调用语法。例如,以下是使用脚本的完整路径的方法:

      . /path/to/set_env_vars.sh
      

      如果您与脚本位于同一目录中,请执行以下操作:

      . set_env_vars.sh
      

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

      这与调用source set_env_vars.sh 相同,但键入起来更短,并且可能在source 不支持的某些地方工作。

      【讨论】:

      • 换句话说,点空间是bash在其他shell中的source的替代品。
      • 我不知道它是如何工作或为什么工作,但它工作得很好。
      • 这个答案应该在顶部
      • Jip 应该在顶部。只是说明显而易见的......如果脚本在您的 PWD 中,那么它的形式为点空间点,例如 . ./localscript.sh
      • 点就是source
      【解决方案4】:

      在 OS X bash 下,您可以执行以下操作:
      创建 bash 脚本文件以取消设置变量

      #!/bin/bash
      unset http_proxy
      

      使文件可执行

      sudo chmod 744 unsetvar
      

      创建别名

      alias unsetvar='source /your/path/to/the/script/unsetvar'
      

      只要您将包含脚本文件的文件夹附加到路径中,它就应该可以使用了。

      【讨论】:

      • 有什么理由不简单地使用alias unsetvar='unset http_proxy'?或者更好的是创建一个函数unsetvar () { unset http_proxy; }
      • 这不仅适用于 OS X。这也适用于 Linux。如果你写了你正在处理的文件,这个答案也会更好。
      【解决方案5】:

      我使用管道、评估和信号创建了一个解决方案。

      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
      

      它可能适用于任何命令。

      【讨论】:

        【解决方案6】:

        在我的 .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 中运行并设置变量 正如预期和想要的那样。

        【讨论】:

        • 这正是我所需要的(好吧,我不得不更改端口号;)。
        【解决方案7】:

        我多年前就这样做了。如果我没记错的话,我在 .bashrc 和 .cshrc 中都包含了一个别名,带有参数,将设置环境的相应形式别名为通用形式。

        然后,您将在两个 shell 中的任何一个中获取的脚本都有一个最后一种形式的命令,该命令在每个 shell 中都适合别名。

        如果我找到具体的别名,我会发布它们。

        【讨论】:

          【解决方案8】:

          你应该使用模块,见http://modules.sourceforge.net/

          编辑:模块包自 2012 年以来尚未更新,但对于基础知识仍然可以正常工作。所有的新功能、花里胡哨的东西都发生在今天的 lmod 中(我更喜欢它):https://www.tacc.utexas.edu/research-development/tacc-projects/lmod

          【讨论】:

          • 我们在这里广泛使用模块文件,并且 csh/bourne-ish 支持是原因之一。我们有遗留的 csh 脚本、bash 脚本和 python 脚本,它们都从相同的模块文件中获取环境变量设置,而不是需要额外维护的 env.csh、env.sh、env.py 脚本集。此外,模块文件允许您的环境反映版本依赖关系:如果您需要从工具的版本 4 更改为版本 3,而不是手动重置所有 env var,您只需模块交换,一切都会改变。
          • 我找不到如何使用它的例子,我所做的每一次尝试都不成功,有什么提示吗?
          • @AquariusPower 这么多年后我不再推荐模块了,但它的道德继承者是 lmod 参见tacc.utexas.edu/tacc-projects/lmod——我认为它的文档也比旧模块更好,看看是否尝试它对你更好
          • @LiDong - 是的,它自 2012 年以来一直没有更新,但对于基础知识仍然可以。所有新功能、花里胡哨的东西都发生在今天的 lmod 中(我更喜欢它):tacc.utexas.edu/research-development/tacc-projects/lmod
          • 它又活过来了:modules.sourceforge.net。另见env2,宣传为“在脚本语言之间转换环境变量的脚本”。
          【解决方案9】:

          您将无法修改调用者的 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`'
          

          这样做的好处是您只需在一个地方维护列表。 从理论上讲,您甚至可以将列表粘贴到文件中并将cat nvpairfilename 放在“in”和“do”之间。

          这几乎是过去登录 shell 终端设置的方式:脚本会输出要在登录 shell 中执行的语句。别名通常用于简化调用,如“tset vt100”。正如另一个答案中提到的,INN UseNet 新闻服务器中也有类似的功能。

          【讨论】:

          • 我认为这可能是正确的。但我不太清楚'setit' 中应该包含什么才能使其从任一外壳程序中正确运行。你能详细说明一下你的想法吗?
          • 基本上,它会检查 $0 并根据调用它的名称移动到脚本的适当部分。
          • 我认为 Thomas 的意思是,您用一种语言编写 setit 脚本,但随后它会输出一组特定于语言的指令集,调用进程必须是 eval'd
          • 啊哈,我明白你现在在做什么了。呃,这很聪明,但很尴尬。感谢您的澄清。
          • SHELL 变量并不完全可靠。示例:在我的 ArchLinux 系统上,我运行 tcsh,SHELL 设置为 /bin/tcsh。启动 bash 并回显 SHELL 仍然会提供 /bin/tcsh 并且同上将 bash 作为 sh 调用。 SHELL 仅适用于需要设置它的 shell 或具有设置它的 rc 文件的系统,但并非全部都可以。
          【解决方案10】:

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

          【讨论】:

            【解决方案11】:

            您可以指示子进程打印其环境变量(通过调用“env”),然后在父进程中循环打印的环境变量并对这些变量调用“export”。

            以下代码基于Capturing output of find . -print0 into a bash array

            如果父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
            

            【讨论】:

              【解决方案12】:

              在 bash 脚本顶部添加 -l 标志,即

              #!/usr/bin/env bash -l
              
              ...
              
              export NAME1="VALUE1"
              export NAME2="VALUE2"
              

              NAME1NAME2 的值现在将导出到您的当前环境,但是这些更改不是永久性的。如果您希望它们是永久的,您需要将它们添加到您的 .bashrc 文件或其他初始化文件中。

              来自手册页:

              -l Make bash act as if it had been invoked as a login shell (see INVOCATION below).
              

              【讨论】:

              • 不,实际上不起作用。所发生的只是您的脚本认为它在登录 shell 中运行。仍然没有将变量暴露给调用 shell。
              【解决方案13】:

              您的 shell 进程拥有父进程环境的副本,并且无法访问父进程的环境。当您的 shell 进程终止时,您对其环境所做的任何更改都会丢失。获取脚本文件是配置 shell 环境最常用的方法,您可能只想硬着头皮为两种 shell 中的每一种维护一个。

              【讨论】:

              • @KrisRandall 其实答案是正确的!!源脚本.sh == 。脚本.sh
              • 另请参阅:env2,“用于在脚本语言之间转换环境变量的 unix perl 脚本。”这是专门从“相关工具”下的Environment Modules home page 链接的
              • 以这种方式实例化应用程序时,是否可以通过调试器运行 Nodejs 应用程序?这就是我所做的,但似乎无法通过launch.json运行它:“./path-to-script/script.sh .env npm start”
              【解决方案14】:

              你总是可以使用别名

              alias your_env='source ~/scripts/your_env.sh'
              

              【讨论】:

                【解决方案15】:

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

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

                【讨论】:

                  【解决方案16】:

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

                  那么为什么不简单地像

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

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

                  【讨论】:

                    【解决方案17】:

                    这行得通——它不是我会使用的,但它“行得通”。让我们创建一个脚本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
                    %
                    

                    当你退出这个 shell 时,原来的 shell 会接管:

                    % 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。您可能会发现退出命令后的提示出现在有趣的地方。


                    请注意 cmets 中的讨论。这不是我推荐的解决方案,但它确实实现了单个脚本的既定目的,以设置适用于所有 shell 的环境(接受 -i 选项来制作交互式 shell)。您还可以在传递任何其他参数的选项之后添加"$@",这可能使shell 可用作一般的“设置环境和执行命令”工具。如果还有其他参数,您可能希望省略 -i,从而导致:

                    #!/bin/ksh
                    export TEREDO_WORMS=ukelele
                    exec $SHELL "${@-'-i'}"
                    

                    "${@-'-i'}" 位表示'如果参数列表包含至少一个参数,则使用原始参数列表;否则,用-i 代替不存在的参数'。

                    【讨论】:

                    • 嗯,这有点激烈:您正在替换登录 shell。如果你打算这样做......你应该检查这如何影响会话和进程组以及其他事情。例如:您认为托管子进程会发生什么?
                    • 毫无疑问 - 这就是我说我不会使用它的原因。如果你执行两次,你并没有丢失会话或进程组信息;这是基于PID和PID不会改变。在配置文件或登录文件中,它会引导您完成通用语言环境设置脚本。但是,正如我所说,我不会使用它。
                    • 这正是我试图为我的具体案例做的! clearcase 在执行“cleartool setview”时似乎使用了这种技术,这是我试图模仿的。非常感谢!
                    • 您可以简单地调用一个新的 shell,而不是替换现有的 shell。
                    • @JonathonHill:你可以(将新的 shell 作为普通命令运行,而不是 exec)。不这样做的主要原因是你有一个杂散级别的 shell,所以你必须做一个额外的 control-D 才能在那个窗口中注销。
                    【解决方案18】:

                    通过使用 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
                    

                    【讨论】:

                    • Kjetil,伙计,这太棒了。我现在真的很喜欢你的剧本。
                    • 这太棒了!但是如何在 Mac 中做到这一点?
                    • 谢谢,作为 1-liner 它是:gdb -nx -p $$ --batch -ex 'call setenv("foo", "bar")' &gt; &amp; /dev/null
                    • 有趣的方法。当我有时间时,我会研究如何从 OS X 中进行更新。
                    【解决方案19】:

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

                    事实上,更改后的环境变量 'sticks' -- 只要您在父程序/shell 下运行。

                    如果在父(Perl 或 shell)退出后环境变量绝对有必要保留,则父 shell 有必要承担繁重的工作。我在文档中看到的一种方法是让当前脚本使用必要的“导出”语言生成一个可执行文件,然后诱使父 shell 执行它——始终意识到您需要在前面加上如果您试图留下修改后环境的非易失版本,则使用“source”命令。充其量是克鲁格。

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

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

                    【讨论】:

                      【解决方案20】:

                      您可以使用不同的 bash_profile 调用另一个 Bash。 此外,您可以创建特殊的 bash_profile 以在多 bashprofile 环境中使用。

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

                      【讨论】:

                      • 这些都不会影响调用shell。
                      • @Ignacio,在这种情况下,您不需要调用脚本来设置环境变量。 “调用”shell 将设置变量本身。但是如果我们仍然需要将 setter 与主 bashrc 代码分开,我们可以将所有这些函数拆分到单独的文件中,并将其作为库包含在 . .bashrc)。
                      【解决方案21】:

                      除了写作条件取决于 $SHELL/$TERM 的设置,没有。使用 Perl 有什么问题?它无处不在(我想不出一个没有它的 UNIX 变体),而且它会为您省去麻烦。

                      【讨论】:

                      • Perl 如何解决这个问题? Perl程序还是不能设置调用shell的环境变量吧?
                      • 没有。但是,它可以通过 Local::Env 设置它,然后使用 system() 或反引号调用您的 shell 脚本。
                      • 我很确定 system() 或反引号会创建一个新的子 shell,而不是调用启动 Perl 脚本的 shell。
                      猜你喜欢
                      • 2013-09-04
                      • 1970-01-01
                      • 1970-01-01
                      • 2015-03-02
                      • 2013-02-06
                      • 1970-01-01
                      • 2016-12-23
                      • 1970-01-01
                      相关资源
                      最近更新 更多