从子 shell 中设置父 shell 的变量

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

a=3
(a=4)
echo $a
82365 次浏览

你不知道。Subshell 不能访问它的父环境。(至少在 Bash 提供的抽象中是这样的。您可以尝试使用 gdb,或粉碎堆栈,或诸如此类的东西,以秘密获得这样的访问权限。不过,我不建议这么做。)

一种替代方法是 subshell 将赋值语句写入一个临时文件,以便其父文件读取:

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

您可以输出 subshell 中的值,并将 subshell 输出分配给调用者脚本中的一个变量:

# 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

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

# 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)。

子 shell 的全部意义在于它影响 没有调用会话。在 bash 中,子 shell 是一个子进程,其他 shell 不同,但即使这样,子 shell 中的变量设置也不会影响调用者。就是这个意思。

你需要一个 subshell 吗? 如果你只是需要一个组,那么使用大括号:

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

给出 4(小心那个空格)。或者,将变量值写入 stdout 并在调用者中捕获它:

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

避免使用“反勾号”,因为它们不被推崇,而且可能难以阅读。

还有 gdb-bash-variable hack:

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

尽管它总是在全局范围中设置一个变量,而不仅仅是父范围

如果问题与 while 循环有关,解决这个问题的方法之一就是使用 Process Substittion:

    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

若要更改从父脚本调用的脚本中的变量,可以在脚本前面调用“”

(编辑-解释) 在大多数贝壳里是“来源”的别名。Source 命令只是在执行脚本中的该位置插入另一个文件的文本。在这个问题的上下文中,这个答案避免使用子 shell

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

文件名为 Script.sh

a=4

预期产出

3
4

除非您可以将所有 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”是存储值的目录。我希望它是一个内存磁盘,以提高速度和波动性:

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"

使用 access 函数的唯一真正原因是应用默认值,因为如果给定的文件不存在,cat 将出错。应用其他 get/set 逻辑也很有用。否则,就根本不需要它了。

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

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

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

不要从父 shell 访问变量,而是更改命令的顺序并使用 工序替代工序替代:

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

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

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

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

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

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


myfunc A B
echo $A
> foo
echo $B
> bar

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

通过使用临时文件方法读取 @ ruakh中的 回答(谢谢)以及要求文件描述符解决方案的注释,我得到了以下想法:

a=3
. <(echo a=4; echo b=5)
echo $a
echo $b
  • 它允许同时返回不同的变量(这可能是已接受答案的 subshell 变量中的一个问题)。
  • 不需要迭代,
  • 没有需要处理的临时文件。
  • 接近 OP 提出的语法。

结果:

4
5

启用 xtrace 后,我们可以从为 subshell 输出创建的文件描述符中获取源代码:

+ a=3
+ . /dev/fd/63 # <-- the file descriptor ;)
++ echo a=4
++ echo b=5
++ a=4
++ b=5
+ echo 4
4
+ echo 5
5