【问题标题】:Set a parent shell's variable from a subshell从子 shell 设置父 shell 的变量
【发布时间】:2023-03-30 05:54:02
【问题描述】:

如何从子 shell 中设置父 shell 中的变量?

a=3
(a=4)
echo $a

【问题讨论】:

标签: bash shell subshell


【解决方案1】:

子shell 的全部意义在于它影响调用会话。在 bash 中,子 shell 是一个子进程,其他 shell 有所不同,但即便如此,子 shell 中的变量设置也不会影响调用者。根据定义。

你需要一个子shell吗?如果您只需要一个组,请使用大括号:

a=3
{ a=4;}
echo $a

给出4(注意那个空格)。或者,将变量值写入标准输出并在调用者中捕获:

a=3
a=$(a=4;echo $a)
echo $a

避免使用反引号``,它们已被弃用并且可能难以阅读。

【讨论】:

  • 感谢您指出花括号。我见过它们,但从来不知道它们做了什么,也没有使用它们。好建议!
  • 管道时有什么方法可以使用它吗?例如。 { false; a=$?;} | true 不会将全局(?)a 设置为所需的值 :(
  • @ArchimedesMP:管道用于使用标准流在进程之间传递数据,这里不涉及任何进程(shell 除外)。你想达到什么目的?
  • 我想在脚本中过滤来自第三方工具 stderr 的无意义警告,而不弄乱 stderr/stdout 并返回原始退出代码。例如。 { some-tool 2>&1 1>&3; ec=$?; } | grep -v "Ignore some warning" 3>&1 1>&2; exit $ecX>&Y 可能有点不对劲,因为我现在无法访问脚本)。 [&感谢您对 4 年前的评论的回复!]
  • @ArchimedesMP:在一行中听起来有点复杂,我会选择一个包装脚本或一个函数来过滤掉它(你可以重定向一个函数,例如myfunc() { echo stuff and nonsense; } > gash.txt
【解决方案2】:

有 gdb-bash-variable hack:

gdb --batch-silent -ex "attach $$" -ex 'set bind_variable("a", "4", 0)'; 

虽然它总是在全局范围内设置一个变量,而不仅仅是父范围

【讨论】:

  • 虽然危险性较小,但我认为这应该与实际脚本中的分叉炸弹一样频繁地使用。
  • 我必须 ..."attach $PPID"... 才能完成这项工作 - $$ 包含当前 shell 的 PID。
  • @dingalapadum 不在 OP 的括号内。可能子shell在同一个进程中运行,然后只是重置变量
  • @BeniBela 在() 子shell 中,$$ 保持不变。实际 subshel​​l 的 PID 可以通过$BASHPID 看到。
【解决方案3】:

你没有。子shell 无权访问其父环境。 (至少在 Bash 提供的抽象范围内。您可能会尝试使用 gdb,或粉碎堆栈等,以秘密获得此类访问权限。不过,我不建议这样做。)

另一种方法是子外壳将赋值语句写入临时文件以供其父级读取:

a=3
(echo 'a=4' > tmp)
. tmp
rm tmp
echo "$a"

【讨论】:

  • 不漂亮,但在某些情况下几乎是更好的选择。谢谢。
  • 如果您使用子 shell 收集父 shell 稍后需要的多个变量的值,文件方法非常有用,特别是在 使用subshel​​l 很重要。
  • 我不得不使用它,因为我在主脚本的开头使用了“set -u”和“set -e”,但我需要运行一些命令来设置一个退出的变量非零退出代码(从而触发“-e”终止脚本)。
  • 文件描述符可以做得更优雅吗?是否可以从子 shell 和父 shell 等访问文件描述符?
【解决方案4】:

如果问题与 while 循环有关,解决此问题的一种方法是使用进程替换:

    var=0
    while read i;
    do
      # perform computations on $i
      ((var++))
    done < <(find . -type f -name "*.bin" -maxdepth 1)

如图所示:https://stackoverflow.com/a/13727116/2547445

【讨论】:

    【解决方案5】:

    要更改从父脚本调用的脚本中的变量,您可以调用以“.”开头的脚本

    a=3
    echo $a    
    . ./calledScript.sh
    echo $a
    

    在被调用的Script.sh中

    a=4
    

    预期输出

    3
    4
    

    【讨论】:

    • 嗨@Carl。这可行,但可以解释答案吗?
    • @Ajeetkumar 请找到指向unix.stackexchange.com/a/114301 的链接。是 source cammand ss64.com/bash/source.html 的快捷方式
    • calledScript.sh 在当前 shell 中运行,因此它不符合“子shell”的条件。
    • 点 ivan_pozdeev,从技术上讲,我没有回答问题,因为它被问到了
    • 如果你使用 shell 脚本,就没有子 shell
    【解决方案6】:

    您可以在子shell中输出值并将子shell输出分配给调用者脚本中的变量:

    # subshell.sh
    echo Value
    
    # caller
    myvar=$(subshell.sh)
    

    如果子shell有更多输出,您可以通过将变量值和其他消息重定向到不同的输出流来分离它们:

    # subshell.sh
    echo "Writing value" 1>&2
    echo Value
    
    # caller
    myvar=$(subshell.sh 2>/dev/null) # or to somewhere else
    echo $myvar
    

    或者,您可以在子shell中输出变量赋值,在调用者脚本中评估它们并避免使用文件来交换信息:

    # subshell.sh
    echo "a=4"
    
    # caller
    # export $(subshell.sh) would be more secure, since export accepts name=value only.
    eval $(subshell.sh)
    echo $a
    

    我能想到的最后一种方法是使用退出代码,但这仅涵盖整数值交换(并且在有限范围内),并打破了解释退出代码的惯例(0 表示成功,0 表示其他所有内容)。

    【讨论】:

      【解决方案7】:

      除非您可以将所有 io 应用到管道并使用文件句柄,否则在 $(command) 和任何其他子进程中基本的变量更新是不可能的。

      然而,常规文件是 bash 用于常规顺序处理的全局变量。注意:由于竞态条件,这种简单的方法不适合并行处理。

      像这样创建一个 set/get/default 函数:

      globalVariable() { # NEW-VALUE
          # set/get/default globalVariable
          if [ 0 = "$#" ]; then
              # new value not given -- echo the value
              [ -e "$aRam/globalVariable" ] \
                  && cat "$aRam/globalVariable" \
                  || printf "default-value-here"
          else
              # new value given -- set the value
              printf "%s" "$1" > "$aRam/globalVariable"
          fi
      }
      

      "$aRam" 是存储值的目录。我喜欢它作为速度和波动性的 ram 磁盘:

      aRam="$(mktemp -td $(basename "$0").XXX)" # temporary directory
      mount -t tmpfs ramdisk "$aRam" # mount the ram disk there
      trap "umount "$aRam" && rm -rf "$aRam"" EXIT # auto-eject
      

      读取值:

      v="$(globalVariable)" # or part of any command
      

      设置值:

      globalVariable newValue # newValue will be written to file
      

      要取消设置值:

      rm -f "$aRam/globalVariable"
      

      访问函数的唯一真正原因是应用默认值,因为 cat 会在文件不存在的情况下出错。应用其他获取/设置逻辑也很有用。否则,根本不需要它。

      一种避免cat不存在文件错误的丑陋读取方法:

      v="$(cat "$aRam/globalVariable 2>/dev/null")"
      

      这种混乱的一个很酷的功能是您可以在程序运行时打开另一个终端并检查文件的内容。

      【讨论】:

      • 容易出现竞争条件,因为对文件的访问不同步。
      【解决方案8】:

      不要从父 shell 访问变量,而是更改命令的顺序并使用process substitution

      a=3
      echo 5 | (read a)
      echo $a
      

      打印 3

      a=3
      read a < <(echo 5)
      echo $a
      

      打印 5

      另一个例子:

      let i=0 
      seq $RANDOM | while read r
                    do 
                      let i=r 
                    done
      echo $i
      

      let i=0
      while read r 
      do 
        let i=r 
      done < <(seq $RANDOM)
      echo $i
      

      或者,当作业控制处于非活动状态时(例如在脚本中),您可以使用lastpipe shell 选项来获得相同的结果,而无需更改命令的顺序:

      #!/bin/bash
      shopt -s lastpipe
      let i=0
      seq $RANDOM | while read r
                    do 
                      let i=r
                    done
      echo $i
      

      【讨论】:

      • 使用read 允许传递多个变量。子外壳从父外壳继承变量,根据需要修改它们,然后退出将它们打印出来。这样就很容易隔离大跨度的代码,例如循环体。
      【解决方案9】:

      虽然从子 shell 中获取多个变量比较困难,但您可以在函数内设置多个变量,而无需使用全局变量。

      您可以将变量的名称传递给使用local -n的函数将其转换为称为nameref的特殊变量:

      myfunc() {
          local -n OUT=$1
          local -n SIDEEFFECT=$2
          OUT='foo'
          SIDEEFFECT='bar'
      }
      
      myfunc A B
      echo $A
      > foo
      echo $B
      > bar
      

      这是我最终使用的技术,而不是让 subshel​​l FOO=$(myfunc) 工作设置多个变量。

      【讨论】:

        【解决方案10】:

        一个非常简单实用的允许多个变量的方法如下,最终可能会在调用中添加参数:

        function ComplexReturn(){
          # do your processing... 
          a=123
          b=456
          echo -n "AAA=${a}; BBB=${b};"
        }
        # ... this can be internal function or any subshell command
        eval $(ComplexReturn)
        echo $AAA $BBB
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-09-23
          • 1970-01-01
          • 1970-01-01
          • 2013-01-08
          • 2013-02-06
          • 1970-01-01
          • 1970-01-01
          • 2011-05-22
          相关资源
          最近更新 更多