bash 如何在bash中的函数内修改全局变量?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/23564995/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-10 00:50:26  来源:igfitidea点击:

How to modify a global variable within a function in bash?

bashvariablesglobal-variableseval

提问by harrison4

I'm working with this:

我正在处理这个:

GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu)

I have a script like below:

我有一个像下面这样的脚本:

#!/bin/bash

e=2

function test1() {
  e=4
  echo "hello"
}

test1 
echo "$e"

Which returns:

返回:

hello
4

But if I assign the result of the function to a variable, the global variable eis not modified:

但是如果我将函数的结果赋给一个变量,全局变量e不会被修改:

#!/bin/bash

e=2

function test1() {
  e=4
  echo "hello"
}

ret=$(test1)

echo "$ret"
echo "$e"

Returns:

返回:

hello
2

I've heard of the use of evalin this case, so I did this in test1:

我听说过在这种情况下使用 eval,所以我这样做了test1

eval 'e=4'

But the same result.

但同样的结果。

Could you explain me why it is not modified? How could I save the echo of the test1function in retand modify the global variable too?

你能解释一下为什么它没有被修改吗?我怎样才能保存test1函数的回声ret并修改全局变量?

采纳答案by Josh Jolly

When you use a command substitution (ie the $(...)construct), you are creating a subshell. Subshells inherit variables from their parent shells, but this only works one way - a subshell cannot modify the environment of its parent shell. Your variable eis set within a subshell, but not the parent shell. There are two ways to pass values from a subshell to its parent. First, you can output something to stdout, then capture it with a command substitution:

当您使用命令替换(即$(...)构造)时,您正在创建一个子shell。子shell 从它们的父shell 继承变量,但这只是一种方式——子shell 不能修改其父shell 的环境。您的变量e是在子 shell 中设置的,但不是在父 shell 中设置的。有两种方法可以将值从子 shell 传递到其父级。首先,您可以将某些内容输出到 stdout,然后使用命令替换来捕获它:

myfunc() {
    echo "Hello"
}

var="$(myfunc)"

echo "$var"

Gives:

给出:

Hello

For a numerical value from 0-255, you can use returnto pass the number as the exit status:

对于 0-255 之间的数值,您可以使用return该数字作为退出状态传递:

mysecondfunc() {
    echo "Hello"
    return 4
}

var="$(mysecondfunc)"
num_var=$?

echo "$var - num is $num_var"

Gives:

给出:

Hello - num is 4

回答by Tino

This needs bash 4.1 if you use {fd}or local -n.

The rest should work in bash 3.x I hope. I am not completely sure due to printf %q- this might be a bash 4 feature.

如果您使用{fd}或 ,则需要 bash 4.1 local -n

我希望其余的应该在 bash 3.x 中工作。我不完全确定,因为printf %q- 这可能是 bash 4 的一个特性。

Summary

概括

Your example can be modified as follows to archive the desired effect:

您可以按如下方式修改您的示例以存档所需的效果:

# Add following 4 lines:
_passback() { while [ 1 -lt $# ]; do printf '%q=%q;' "" "${!1}"; shift; done; return ; }
passback() { _passback "$@" "$?"; }
_capture() { { out="$("${@:2}" 3<&-; "_" >&3)"; ret=$?; printf "%q=%q;" "" "$out"; } 3>&1; echo "(exit $ret)"; }
capture() { eval "$(_capture "$@")"; }

e=2

# Add following line, called "Annotation"
function test1_() { passback e; }
function test1() {
  e=4
  echo "hello"
}

# Change following line to:
capture ret test1 

echo "$ret"
echo "$e"

prints as desired:

根据需要打印:

hello
4

Note that this solution:

请注意,此解决方案:

  • Works for e=1000, too.
  • Preserves $?if you need $?
  • 也适用于e=1000
  • $?如果需要,可以保留$?

The only bad sideffects are:

唯一不好的副作用是:

  • It needs a modern bash.
  • It forks quite more often.
  • It needs the annotation (named after your function, with an added _)
  • It sacrifices file descriptor 3.
    • You can change it to another FD if you need that.
      • In _capturejust replace all occurances of 3with another (higher) number.
  • 它需要一个现代的bash.
  • 它更频繁地分叉。
  • 它需要注释(以您的函数命名,并添加_
  • 它牺牲了文件描述符 3。
    • 如果需要,您可以将其更改为另一个 FD。
      • _capture刚刚替换所有出现3另一个(更高)的数量。

The following (which is quite long, sorry for that) hopefully explains, how to adpot this recipe to other scripts, too.

以下(很长,抱歉)希望解释如何将此配方也应用到其他脚本中。

The problem

问题

d() { let x++; date +%Y%m%d-%H%M%S; }

x=0
d1=$(d)
d2=$(d)
d3=$(d)
d4=$(d)
echo $x $d1 $d2 $d3 $d4

outputs

产出

0 20171129-123521 20171129-123521 20171129-123521 20171129-123521

while the wanted output is

而想要的输出是

4 20171129-123521 20171129-123521 20171129-123521 20171129-123521

The cause of the problem

问题的原因

Shell variables (or generally speaking, the environment) is passed from parental processes to child processes, but not vice versa.

Shell 变量(或一般来说,环境)从父进程传递到子进程,但反之则不然。

If you do output capturing, this usually is run in a subshell, so passing back variables is difficult.

如果您进行输出捕获,这通常是在子 shell 中运行的,因此传回变量是很困难的。

Some even tell you, that it is impossible to fix. This is wrong, but it is a long known difficult to solve problem.

有些人甚至告诉你,这是不可能修复的。这是错误的,但这是一个众所周知的难以解决的问题。

There are several ways on how to solve it best, this depends on your needs.

有几种方法可以最好地解决它,这取决于您的需求。

Here is a step by step guide on how to do it.

这是有关如何执行此操作的分步指南。

Passing back variables into the parental shell

将变量传回父 shell

There is a way to pass back variables to a parental shell. However this is a dangerous path, because this uses eval. If done improperly, you risk many evil things. But if done properly, this is perfectly safe, provided that there is no bug in bash.

有一种方法可以将变量传回给父母 shell。然而,这是一条危险的路径,因为它使用eval. 如果操作不当,您将面临许多邪恶的风险。但是如果操作得当,这是完全安全的,前提是bash.

_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "" "${!1}"; shift; done; }

d() { let x++; d=$(date +%Y%m%d-%H%M%S); _passback x d; }

x=0
eval `d`
d1=$d
eval `d`
d2=$d
eval `d`
d3=$d
eval `d`
d4=$d
echo $x $d1 $d2 $d3 $d4

prints

印刷

4 20171129-124945 20171129-124945 20171129-124945 20171129-124945

Note that this works for dangerous things, too:

请注意,这也适用于危险的事情:

danger() { danger="$*"; passback danger; }
eval `danger '; /bin/echo *'`
echo "$danger"

prints

印刷

; /bin/echo *

This is due to printf '%q', which quotes everything such, that you can re-use it in a shell context safely.

这是由于printf '%q',它引用了所有内容,因此您可以安全地在 shell 上下文中重新使用它。

But this is a pain in the a..

但这是一个痛苦的..

This does not only look ugly, it also is much to type, so it is error prone. Just one single mistake and you are doomed, right?

这不仅看起来难看,而且打字也很多,所以很容易出错。只要一个错误,你就注定要失败,对吗?

Well, we are at shell level, so you can improve it. Just think about an interface you want to see, and then you can implement it.

好吧,我们处于 shell 级别,因此您可以对其进行改进。想一个你想看到的接口,然后你就可以实现它。

Augment, how the shell processes things

增强,外壳如何处理事物

Let's go a step back and think about some API which allows us to easily express, what we want to do.

让我们退后一步,考虑一些可以让我们轻松表达我们想要做什么的 API。

Well, what do we want do do with the d()function?

那么,我们想用这个d()函数做什么?

We want to capture the output into a variable. OK, then let's implement an API for exactly this:

我们想将输出捕获到一个变量中。好的,那么让我们为此实现一个 API:

# This needs a modern bash 4.3 (see "help declare" if "-n" is present,
# we get rid of it below anyway).
: capture VARIABLE command args..
capture()
{
local -n output=""
shift
output="$("$@")"
}

Now, instead of writing

现在,而不是写

d1=$(d)

we can write

我们可以写

capture d1 d

Well, this looks like we haven't changed much, as, again, the variables are not passed back from dinto the parent shell, and we need to type a bit more.

嗯,这看起来我们没有太大变化,因为变量没有从d父 shell传回,我们需要输入更多内容。

However now we can throw the full power of the shell at it, as it is nicely wrapped in a function.

但是现在我们可以使用 shell 的全部功能,因为它很好地包装在一个函数中。

Think about an easy to reuse interface

考虑一个易于重用的界面

A second thing is, that we want to be DRY (Don't Repeat Yourself). So we definitively do not want to type something like

第二件事是,我们想要保持干燥(不要重复自己)。所以我们绝对不想输入类似的东西

x=0
capture1 x d1 d
capture1 x d2 d
capture1 x d3 d
capture1 x d4 d
echo $x $d1 $d2 $d3 $d4

The xhere is not only redundant, it's error prone to always repeate in the correct context. What if you use it 1000 times in a script and then add a variable? You definitively do not want to alter all the 1000 locations where a call to dis involved.

x这里不仅是多余的,它的错误倾向始终在正确的上下文repeate。如果您在脚本中使用它 1000 次,然后添加一个变量会怎样?您绝对不想更改d涉及呼叫的所有 1000 个位置。

So leave the xaway, so we can write:

所以离开x,所以我们可以写:

_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "" "${!1}"; shift; done; }

d() { let x++; output=$(date +%Y%m%d-%H%M%S); _passback output x; }

xcapture() { local -n output=""; eval "$("${@:2}")"; }

x=0
xcapture d1 d
xcapture d2 d
xcapture d3 d
xcapture d4 d
echo $x $d1 $d2 $d3 $d4

outputs

产出

4 20171129-132414 20171129-132414 20171129-132414 20171129-132414

This already looks very good. (But there still is the local -nwhich does not work in oder common bash3.x)

这看起来已经很不错了。(但仍然存在local -n在 oder common bash3.x 中不起作用的情况)

Avoid changing d()

避免改变 d()

The last solution has some big flaws:

最后一个解决方案有一些大缺陷:

  • d()needs to be altered
  • It needs to use some internal details of xcaptureto pass the output.
    • Note that this shadows (burns) one variable named output, so we can never pass this one back.
  • It needs to cooperate with _passback
  • d()需要改变
  • 它需要使用一些内部细节xcapture来传递输出。
    • 请注意,这会遮蔽(燃烧)一个名为 的变量output,因此我们永远无法将其传回。
  • 它需要与 _passback

Can we get rid of this, too?

我们也能摆脱这个吗?

Of course, we can! We are in a shell, so there is everything we need to get this done.

我们当然可以!我们在一个 shell 中,所以我们需要完成这一切。

If you look a bit closer to the call to evalyou can see, that we have 100% control at this location. "Inside" the evalwe are in a subshell, so we can do everything we want without fear of doing something bad to the parental shell.

如果您靠近呼叫,eval您可以看到,我们在此位置拥有 100% 的控制权。“内部”eval我们在一个子壳中,所以我们可以做任何我们想做的事,而不必担心会对父母壳做坏事。

Yeah, nice, so let's add another wrapper, now directly inside the eval:

是的,很好,所以让我们添加另一个包装器,现在直接在eval

_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "" "${!1}"; shift; done; }
# !DO NOT USE!
_xcapture() { "${@:2}" > >(printf "%q=%q;" "" "$(cat)"); _passback x; }  # !DO NOT USE!
# !DO NOT USE!
xcapture() { eval "$(_xcapture "$@")"; }

d() { let x++; date +%Y%m%d-%H%M%S; }

x=0
xcapture d1 d
xcapture d2 d
xcapture d3 d
xcapture d4 d
echo $x $d1 $d2 $d3 $d4

prints

印刷

4 20171129-132414 20171129-132414 20171129-132414 20171129-132414                                                    

However, this, again, has some major drawback:

然而,这也有一些主要的缺点:

  • The !DO NOT USE!markers are there, because there is a very bad race condition in this, which you cannot see easily:
    • The >(printf ..)is a background job. So it might still execute while the _passback xis running.
    • You can see this yourself if you add a sleep 1;before printfor _passback. _xcapture a d; echothen outputs xor afirst, respectively.
  • The _passback xshould not be part of _xcapture, because this makes it difficult to reuse that recipe.
  • Also we have some unneded fork here (the $(cat)), but as this solution is !DO NOT USE!I took the shortest route.
  • !DO NOT USE!标记是有,因为在这个非常糟糕的比赛条件,你可以不看很容易:
    • >(printf ..)是一个后台工作。所以它可能在_passback x运行时仍然执行。
    • 如果您添加sleep 1;beforeprintf或 ,您可以自己看到这一点_passback_xcapture a d; echo然后分别输出xor afirst 。
  • _passback x不应该成为其中的一部分_xcapture,因为这使得它很难重用那几招。
  • 我们这里也有一些不需要的叉子 (the $(cat)),但由于这个解决方案,!DO NOT USE!我选择了最短的路线。

However, this shows, that we can do it, without modification to d()(and without local -n)!

然而,这表明,我们可以做到,而无需修改d()(和没有local -n)!

Please note that we not neccessarily need _xcaptureat all, as we could have written everyting right in the eval.

请注意,我们根本不需要_xcapture,因为我们可以在eval.

However doing this usually isn't very readable. And if you come back to your script in a few years, you probably want to be able to read it again without much trouble.

然而,这样做通常不是很可读。如果你在几年后回到你的剧本,你可能希望能够毫无困难地再次阅读它。

Fix the race

修复比赛

Now let's fix the race condition.

现在让我们修复竞争条件。

The trick could be to wait until printfhas closed it's STDOUT, and then output x.

诀窍可能是等到printf关闭它的 STDOUT,然后输出x.

There are many ways to archive this:

有很多方法可以存档:

  • You cannot use shell pipes, because pipes run in different processes.
  • One can use temporary files,
  • or something like a lock file or a fifo. This allows to wait for the lock or fifo,
  • or different channels, to output the information, and then assemble the output in some correct sequence.
  • 您不能使用 shell 管道,因为管道在不同的进程中运行。
  • 可以使用临时文件,
  • 或诸如锁定文件或fifo之类的东西。这允许等待锁定或先进先出,
  • 或不同的通道,输出信息,然后以正确的顺序组合输出。

Following the last path could look like (note that it does the printflast because this works better here):

遵循最后一条路径可能看起来像(请注意,它执行printf最后一条,因为这在这里效果更好):

_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "" "${!1}"; shift; done; }

_xcapture() { { printf "%q=%q;" "" "$("${@:2}" 3<&-; _passback x >&3)"; } 3>&1; }

xcapture() { eval "$(_xcapture "$@")"; }

d() { let x++; date +%Y%m%d-%H%M%S; }

x=0
xcapture d1 d
xcapture d2 d
xcapture d3 d
xcapture d4 d
echo $x $d1 $d2 $d3 $d4

outputs

产出

4 20171129-144845 20171129-144845 20171129-144845 20171129-144845

Why is this correct?

为什么这是正确的?

  • _passback xdirectly talks to STDOUT.
  • However, as STDOUT needs to be captured in the inner command, we first "save" it into FD3 (you can use others, of course) with '3>&1' and then reuse it with >&3.
  • The $("${@:2}" 3<&-; _passback x >&3)finishes after the _passback, when the subshell closes STDOUT.
  • So the printfcannot happen before the _passback, regardless how long _passbacktakes.
  • Note that the printfcommand is not executed before the complete commandline is assembled, so we cannot see artefacts from printf, independently how printfis implemented.
  • _passback x直接与 STDOUT 对话。
  • 但是,由于需要在内部命令中捕获 STDOUT,我们首先使用 '3>&1' 将其“保存”到 FD3(当然,您可以使用其他的),然后使用>&3.
  • $("${@:2}" 3<&-; _passback x >&3)后结束_passback,当子shell关闭STDOUT。
  • 因此,无论需要多长时间,都printf不会在 之前发生。_passback_passback
  • 请注意,在printf组装完整的命令行之前不会执行该命令,因此我们无法看到来自 的人工制品printf,独立于如何printf实现。

Hence first _passbackexecutes, then the printf.

因此首先_passback执行,然后printf.

This resolves the race, sacrificing one fixed file descriptor 3. You can, of course, choose another file descriptor in the case, that FD3 is not free in your shellscript.

这解决了竞争,牺牲了一个固定的文件描述符 3。当然,您可以选择另一个文件描述符,以防 FD3 在您的 shellscript 中不是空闲的。

Please also note the 3<&-which protects FD3 to be passed to the function.

还请注意3<&-保护 FD3 传递给函数的那个​​。

Make it more generic

让它更通用

_capturecontains parts, which belong to d(), which is bad, from a reusability perspective. How to solve this?

_captured()从可重用性的角度来看,包含属于 的部分是坏的。如何解决这个问题?

Well, do it the desparate way by introducing one more thing, an additional function, which must return the right things, which is named after the original function with _attached.

好吧,通过引入另外一个东西,一个额外的函数来做这件事,它必须返回正确的东西,它以原始函数命名并_附加。

This function is called after the real function, and can augment things. This way, this can be read as some annotation, so it is very readable:

这个函数在真正的函数之后被调用,并且可以增加事物。这样,this 就可以读作一些注解,所以可读性很强:

_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "" "${!1}"; shift; done; }
_capture() { { printf "%q=%q;" "" "$("${@:2}" 3<&-; "_" >&3)"; } 3>&1; }
capture() { eval "$(_capture "$@")"; }

d_() { _passback x; }
d() { let x++; date +%Y%m%d-%H%M%S; }

x=0
capture d1 d
capture d2 d
capture d3 d
capture d4 d
echo $x $d1 $d2 $d3 $d4

still prints

仍然打印

4 20171129-151954 20171129-151954 20171129-151954 20171129-151954

Allow access to the return-code

允许访问返回码

There is only on bit missing:

只有一点点缺失:

v=$(fn)sets $?to what fnreturned. So you probably want this, too. It needs some bigger tweaking, though:

v=$(fn)设置$?fn返回的内容。所以你可能也想要这个。不过,它需要一些更大的调整:

# This is all the interface you need.
# Remember, that this burns FD=3!
_passback() { while [ 1 -lt $# ]; do printf '%q=%q;' "" "${!1}"; shift; done; return ; }
passback() { _passback "$@" "$?"; }
_capture() { { out="$("${@:2}" 3<&-; "_" >&3)"; ret=$?; printf "%q=%q;" "" "$out"; } 3>&1; echo "(exit $ret)"; }
capture() { eval "$(_capture "$@")"; }

# Here is your function, annotated with which sideffects it has.
fails_() { passback x y; }
fails() { x=; y=69; echo FAIL; return 23; }

# And now the code which uses it all
x=0
y=0
capture wtf fails 42
echo $? $x $y $wtf

prints

印刷

23 42 69 FAIL

There is still a lot room for improvement

仍有很大的改进空间

  • _passback()can be elmininated with passback() { set -- "$@" "$?"; while [ 1 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; return $1; }
  • _capture()can be eliminated with capture() { eval "$({ out="$("${@:2}" 3<&-; "$2_" >&3)"; ret=$?; printf "%q=%q;" "$1" "$out"; } 3>&1; echo "(exit $ret)")"; }

  • The solution pollutes a file descriptor (here 3) by using it internally. You need to keep that in mind if you happen to pass FDs.
    Note thatbash4.1 and above has {fd}to use some unused FD.
    (Perhaps I will add a solution here when I come around.)
    Note that this is why I use to put it in separate functions like _capture, because stuffing this all into one line is possible, but makes it increasingly harder to read and understand

  • Perhaps you want to capture STDERR of the called function, too. Or you want to even pass in and out more than one filedescriptor from and to variables.
    I have no solution yet, however here is a way to catch more than one FD, so we can probably pass back the variables this way, too.

  • _passback()可以用 passback() { set -- "$@" "$?"; while [ 1 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; return $1; }
  • _capture()可以消除 capture() { eval "$({ out="$("${@:2}" 3<&-; "$2_" >&3)"; ret=$?; printf "%q=%q;" "$1" "$out"; } 3>&1; echo "(exit $ret)")"; }

  • 该解决方案通过在内部使用它来污染文件描述符(此处为 3)。如果您碰巧通过了 FD,则需要牢记这一点。
    注意bash4.1及以上必须{fd}使用一些未使用的FD。
    (也许当我来的时候我会在这里添加一个解决方案。)
    请注意,这就是为什么我习惯将它放在单独的函数中,例如_capture,因为将所有这些都塞到一行中是可能的,但会使阅读和理解变得越来越困难

  • 也许您也想捕获被调用函数的 STDERR。或者您甚至希望从变量传入和传出多个文件描述符。
    我还没有解决方案,但是这里有一种方法可以捕获多个 FD,因此我们也可以通过这种方式传回变量。

Also do not forget:

也不要忘记:

This must call a shell function, not an external command.

这必须调用 shell 函数,而不是外部命令。

There is no easy way to pass environment variables out of external commands. (With LD_PRELOAD=it should be possible, though!) But this then is something completely different.

没有简单的方法可以从外部命令中传递环境变量。(LD_PRELOAD=虽然它应该是可能的!)但这是完全不同的东西。

Last words

最后的话

This is not the only possible solution. It is one example to a solution.

这不是唯一可能的解决方案。这是一个解决方案的例子。

As always you have many ways to express things in the shell. So feel free to improve and find something better.

与往常一样,您可以通过多种方式在 shell 中表达内容。因此,请随时改进并找到更好的东西。

The solution presented here is quite far from being perfect:

这里提出的解决方案远非完美:

  • It was nearly not testet at all, so please forgive typos.
  • There is a lot of room for improvement, see above.
  • It uses many features from modern bash, so probably is hard to port to other shells.
  • And there might be some quirks I haven't thought about.
  • 它几乎根本不是 testet,所以请原谅错别字。
  • 有很大的改进空间,见上文。
  • 它使用了许多现代功能bash,因此可能很难移植到其他 shell。
  • 可能还有一些我没有想到的怪癖。

However I think it is quite easy to use:

但是我认为它很容易使用:

  • Add just 4 lines of "library".
  • Add just 1 line of "annotation" for your shell function.
  • Sacrifices just one file descriptor temporarily.
  • And each step should be easy to understand even years later.
  • 仅添加 4 行“库”。
  • 为您的 shell 函数添加 1 行“注释”。
  • 暂时只牺牲一个文件描述符。
  • 而且每一步都应该很容易理解,即使是在多年之后。

回答by PradyJord

What you are doing, you are executing test1

你在做什么,你正在执行 test1

$(test1)

$(test1)

in a sub-shell( child shell ) and Child shells cannot modify anything in parent.

在子壳(子壳)中,子壳不能修改父壳中的任何内容

You can find it in bash manual

您可以在 bash手册中找到它

Please Check: Things results in a subshell here

请检查:这里的结果是一个子shell

回答by Ashkan

Maybe you can use a file, write to file inside function, read from file after it. I have changed eto an array. In this example blanks are used as separator when reading back the array.

也许你可以使用一个文件,在函数内部写入文件,然后从文件中读取。我已更改e为数组。在这个例子中,当读回数组时,空格被用作分隔符。

#!/bin/bash

declare -a e
e[0]="first"
e[1]="secondddd"

function test1 () {
 e[2]="third"
 e[1]="second"
 echo "${e[@]}" > /tmp/tempout
 echo hi
}

ret=$(test1)

echo "$ret"

read -r -a e < /tmp/tempout
echo "${e[@]}"
echo "${e[0]}"
echo "${e[1]}"
echo "${e[2]}"

Output:

输出:

hi
first second third
first
second
third

回答by Elmar Zander

I had a similar problem, when I wanted to automatically remove temp files I had created. The solution I came up with was not to use command substitution, but rather to pass the name of the variable, that should take the final result, into the function. E.g.

我有一个类似的问题,当我想自动删除我创建的临时文件时。我想出的解决方案不是使用命令替换,而是将变量的名称(应该采用最终结果)传递给函数。例如

#! /bin/bash

remove_later=""
new_tmp_file() {
    file=$(mktemp)
    remove_later="$remove_later $file"
    eval =$file
}
remove_tmp_files() {
    rm $remove_later
}
trap remove_tmp_files EXIT

new_tmp_file tmpfile1
new_tmp_file tmpfile2

So, in your case that would be:

所以,在你的情况下,这将是:

#!/bin/bash

e=2

function test1() {
  e=4
  eval ="hello"
}

test1 ret

echo "$ret"
echo "$e"

Works and has no restrictions on the "return value".

有效并且对“返回值”没有限制。

回答by Fabio

A solution to this problem, without having to introduce complex functions and heavily modify the original one, is to store the value in a temporary file and read / write it when needed.

解决这个问题的方法,无需引入复杂的函数,也无需大量修改原来的函数,将值存储在一个临时文件中,并在需要时读取/写入它。

This approach helped me greatly when I had to mock a bash function called multiple times in a bats test case.

当我不得不在 bats 测试用例中模拟多次调用的 bash 函数时,这种方法对我有很大帮助。

For example, you could have:

例如,你可以有:

# Usage read_value path_to_tmp_file
function read_value {
  cat ""
}

# Usage: set_value path_to_tmp_file the_value
function set_value {
  echo "" > ""
}
#----

# Original code:

function test1() {
  e=4
  set_value "${tmp_file}" "${e}"
  echo "hello"
}


# Create the temp file
# Note that tmp_file is available in test1 as well
tmp_file=$(mktemp)

# Your logic
e=2
# Store the value
set_value "${tmp_file}" "${e}"

# Run test1
test1

# Read the value modified by test1
e=$(read_value "${tmp_file}")
echo "$e"

The drawback is that you might need multiple temp files for different variables. And also you might need to issue a synccommand to persist the contents on the disk between one write and read operations.

缺点是您可能需要针对不同变量的多个临时文件。并且您可能还需要发出一个sync命令来在一次写入和读取操作之间将内容保存在磁盘上。

回答by Some programmer dude

It's because command substitution is performed in a subshell, so while the subshell inherits the variables, changes to them are lost when the subshell ends.

这是因为命令替换是在子shell 中执行的,所以虽然子shell 继承了变量,但是当子shell 结束时对它们的更改会丢失。

Reference:

参考

Command substitution, commands grouped with parentheses, and asynchronous commands are invoked in a subshell environment that is a duplicate of the shell environment

命令替换、用括号分组的命令和异步命令在与 shell 环境重复的子 shell 环境中调用

回答by Dino Dini

You can always use an alias:

您始终可以使用别名:

alias next='printf "blah_%02d" $count;count=$((count+1))'