har*_*on4 83 variables bash eval global-variables
我正在使用这个:
GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu)
Run Code Online (Sandbox Code Playgroud)
我有一个如下脚本:
#!/bin/bash
e=2
function test1() {
e=4
echo "hello"
}
test1
echo "$e"
Run Code Online (Sandbox Code Playgroud)
哪个回报:
hello
4
Run Code Online (Sandbox Code Playgroud)
但是如果我将函数的结果赋给变量,e则不修改全局变量:
#!/bin/bash
e=2
function test1() {
e=4
echo "hello"
}
ret=$(test1)
echo "$ret"
echo "$e"
Run Code Online (Sandbox Code Playgroud)
返回:
hello
2
Run Code Online (Sandbox Code Playgroud)
我听说过在这种情况下使用eval,所以我这样做test1:
eval 'e=4'
Run Code Online (Sandbox Code Playgroud)
但结果相同.
你能解释一下为什么不修改它吗?我怎么能保存test1函数的回声ret并修改全局变量呢?
Jos*_*lly 85
当您使用命令替换(即$(...)构造)时,您正在创建子shell.子shell从其父shell继承变量,但这只能以一种方式工作 - 子shell无法修改其父shell的环境.您的变量e在子shell中设置,但不在父shell中.将值从子shell传递到其父级有两种方法.首先,您可以输出一些内容到stdout,然后使用命令替换来捕获它:
myfunc() {
echo "Hello"
}
var="$(myfunc)"
echo "$var"
Run Code Online (Sandbox Code Playgroud)
得到:
Hello
Run Code Online (Sandbox Code Playgroud)
对于0-255的数值,您可以使用return该数字作为退出状态:
mysecondfunc() {
echo "Hello"
return 4
}
var="$(mysecondfunc)"
num_var=$?
echo "$var - num is $num_var"
Run Code Online (Sandbox Code Playgroud)
得到:
Hello - num is 4
Run Code Online (Sandbox Code Playgroud)
Tin*_*ino 27
您的示例可以按如下方式修改,以存档所需的效果:
# Add following 4 lines:
_passback() { while [ 1 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; return $1; }
passback() { _passback "$@" "$?"; }
_capture() { { out="$("${@:2}" 3<&-; "$2_" >&3)"; ret=$?; printf "%q=%q;" "$1" "$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"
Run Code Online (Sandbox Code Playgroud)
根据需要打印:
hello
4
Run Code Online (Sandbox Code Playgroud)
请注意,此解决方案:
{fd}.local -n如果需要,保留printf %q唯一不好的副作用是:
e=1000.$?)$?刚刚替换所有出现bash另一个(更高)的数量.以下(很长时间,对不起)有希望解释,如何将此配方也用于其他脚本.
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
Run Code Online (Sandbox Code Playgroud)
输出
0 20171129-123521 20171129-123521 20171129-123521 20171129-123521
Run Code Online (Sandbox Code Playgroud)
而想要的输出是
4 20171129-123521 20171129-123521 20171129-123521 20171129-123521
Run Code Online (Sandbox Code Playgroud)
Shell变量(或一般来说,环境)从父进程传递到子进程,但反之亦然.
如果你进行输出捕获,这通常是在子shell中运行,因此传回变量很困难.
有些人甚至告诉你,这是不可能解决的.这是错误的,但长期以来一直难以解决问题.
如何最好地解决它有几种方法,这取决于您的需求.
这是一个如何做的分步指南.
有一种方法可以将变量传递给父shell.然而,这是一条危险的道路,因为它使用了_.如果做得不正确,你会冒许多危险的事情.但如果做得好,这是完全安全的,前提是没有错误_capture.
_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "$1" "${!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
Run Code Online (Sandbox Code Playgroud)
版画
4 20171129-124945 20171129-124945 20171129-124945 20171129-124945
Run Code Online (Sandbox Code Playgroud)
请注意,这也适用于危险的事情:
danger() { danger="$*"; passback danger; }
eval `danger '; /bin/echo *'`
echo "$danger"
Run Code Online (Sandbox Code Playgroud)
版画
; /bin/echo *
Run Code Online (Sandbox Code Playgroud)
这是因为3它引用了所有这些,你可以安全地在shell上下文中重复使用它.
这不仅看起来很难看,而且输入也很多,因此很容易出错.只有一个错误,你注定要失败,对吗?
好吧,我们处于shell级别,所以你可以改进它.只需考虑一下您想要查看的界面,然后就可以实现它.
让我们退一步思考一些API,它可以让我们轻松表达,我们想做什么.
那么,我们想要做什么eval呢?
我们想将输出捕获到变量中.好的,那么让我们实现一个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="$1"
shift
output="$("$@")"
}
Run Code Online (Sandbox Code Playgroud)
现在,而不是写作
d1=$(d)
Run Code Online (Sandbox Code Playgroud)
我们可以写
capture d1 d
Run Code Online (Sandbox Code Playgroud)
好吧,这看起来我们没有太大变化,因为变量不会再传递回bash父shell,我们需要再输入一些.
但是现在我们可以将shell的全部功能抛给它,因为它很好地包含在一个函数中.
第二件事是,我们想要干(不要重复自己).所以我们明确地不想输入类似的东西
x=0
capture1 x d1 d
capture1 x d2 d
capture1 x d3 d
capture1 x d4 d
echo $x $d1 $d2 $d3 $d4
Run Code Online (Sandbox Code Playgroud)
在printf '%q'这里不仅是多余的,这是容易出错,以在正确的上下文总是repeate.如果在脚本中使用1000次然后添加变量怎么办?你明确地不想改变d()涉及呼叫的所有1000个地点.
所以请d离开,所以我们可以写:
_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; }
d() { let x++; output=$(date +%Y%m%d-%H%M%S); _passback output x; }
xcapture() { local -n output="$1"; eval "$("${@:2}")"; }
x=0
xcapture d1 d
xcapture d2 d
xcapture d3 d
xcapture d4 d
echo $x $d1 $d2 $d3 $d4
Run Code Online (Sandbox Code Playgroud)
输出
4 20171129-132414 20171129-132414 20171129-132414 20171129-132414
Run Code Online (Sandbox Code Playgroud)
这看起来非常好.
x最后的解决方案有一些很大的缺陷:
d 需要改变x来传递输出.
local -n,所以我们永远不能将这个变回来.bash我们也可以摆脱这个吗?
我们当然可以!我们处于一个shell中,因此我们需要完成所有这些工作.
如果你看起来更接近d()你的电话,你可以看到,我们在这个位置有100%的控制权."内部" d()我们在子壳中,所以我们可以做我们想做的一切,而不用担心对父母的外壳做坏事.
是的,很好,所以让我们添加另一个包装器,现在直接在xcapture:
_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; }
# !DO NOT USE!
_xcapture() { "${@:2}" > >(printf "%q=%q;" "$1" "$(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
Run Code Online (Sandbox Code Playgroud)
版画
4 20171129-132414 20171129-132414 20171129-132414 20171129-132414
Run Code Online (Sandbox Code Playgroud)
然而,这又有一些主要缺点:
output标记是有,因为在这个非常糟糕的比赛条件,你可以不看很容易:
_passback是一份后台工作.所以在eval运行时它仍然可以执行.eval之前eval或!DO NOT USE!.
>(printf ..)然后输出_passback x或sleep 1;分别输出.printf不应该是其中的一部分_passback,因为这使得重用该配方变得困难._xcapture a d; echo),但是这个解决方案是x我采用了最短的路线.但是,这表明,我们可以做到,而无需修改a!
请注意,我们根本不需要_passback x,因为我们可能已经写好了_xcapture.
但是这样做通常不是很易读.如果你在几年内回到你的脚本,你可能希望能够再次阅读它而不会有太多麻烦.
现在让我们来解决竞争状况.
诀窍可能是等到$(cat)关闭它的STDOUT,然后输出!DO NOT USE!.
存档的方法有很多种:
在最后一条路径之后可能看起来像(请注意它是d()最后一条,因为这在这里效果更好):
_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; }
_xcapture() { { printf "%q=%q;" "$1" "$("${@: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
Run Code Online (Sandbox Code Playgroud)
输出
4 20171129-144845 20171129-144845 20171129-144845 20171129-144845
Run Code Online (Sandbox Code Playgroud)
为什么这是正确的?
local -n 直接与STDOUT交谈._xcapture.eval后结束printf,当子shell关闭STDOUT.x不可能在之前发生printf,无论_passback x需要多长时间.>&3组装完整的命令行之前不会执行该命令,因此我们无法$("${@:2}" 3<&-; _passback x >&3)独立地看到伪像如何_passback实现.因此首先printf执行,然后执行_passback.
这解决了竞争,牺牲了一个固定文件描述符3.当然,你可以选择另一个文件描述符,你的shellcript中FD3不是免费的.
还请注意_passback哪个保护FD3传递给该功能.
printfprintf从可重用性的角度来看,它包含属于哪个部分,这些部分很糟糕.怎么解决这个?
好吧,通过引入一个额外的功能,一个额外的功能,它必须返回正确的东西,这是以原始函数printf附加命名的绝对方式.
这个函数在真实函数之后被调用,并且可以增加事物.这样,这可以作为一些注释读取,因此它非常易读:
_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; }
_capture() { { printf "%q=%q;" "$1" "$("${@:2}" 3<&-; "$2_" >&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
Run Code Online (Sandbox Code Playgroud)
仍然打印
4 20171129-151954 20171129-151954 20171129-151954 20171129-151954
Run Code Online (Sandbox Code Playgroud)
只有一点点缺失:
_passback设置printf为3<&-返回的内容.所以你可能也想要这个.但它需要更大的调整:
# This is all the interface you need.
# Remember, that this burns FD=3!
_passback() { while [ 1 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; return $1; }
passback() { _passback "$@" "$?"; }
_capture() { { out="$("${@:2}" 3<&-; "$2_" >&3)"; ret=$?; printf "%q=%q;" "$1" "$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=$1; 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
Run Code Online (Sandbox Code Playgroud)
版画
23 42 69 FAIL
Run Code Online (Sandbox Code Playgroud)
该解决方案在内部使用它来污染文件描述符.如果您需要在脚本中使用它,则需要非常小心,不要使用它.也许有一种方法可以摆脱这种情况并用动态(免费)文件描述符替换它.
也许你想要捕获被调用函数的STDERR.或者你甚至想要从变量传入和传出多个filedescriptor.
也别忘了:
这必须调用shell函数,而不是外部命令.
没有简单的方法可以将环境变量传递出外部命令.(
_capture虽然它应该是可能的!)但这就完全不同了.
这不是唯一可行的解决方案.这是解决方案的一个例子.
一如既往,你有很多方法可以在shell中表达事物.所以随意改进并找到更好的东西.
这里介绍的解决方案远非完美:
d(),因此可能难以移植到其他外壳.不过我觉得它很容易使用:
Ash*_*kan 13
也许你可以使用一个文件,在函数内写入文件,然后从文件中读取.我已经改成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]}"
Run Code Online (Sandbox Code Playgroud)
输出:
hi
first second third
first
second
third
Run Code Online (Sandbox Code Playgroud)
当我想自动删除创建的临时文件时,我遇到了类似的问题。我想出的解决方案不是使用命令替换,而是将变量的名称(应该将最终结果)传递给函数。例如
#! /bin/bash
remove_later=""
new_tmp_file() {
file=$(mktemp)
remove_later="$remove_later $file"
eval $1=$file
}
remove_tmp_files() {
rm $remove_later
}
trap remove_tmp_files EXIT
new_tmp_file tmpfile1
new_tmp_file tmpfile2
Run Code Online (Sandbox Code Playgroud)
因此,在您的情况下,将是:
#!/bin/bash
e=2
function test1() {
e=4
eval $1="hello"
}
test1 ret
echo "$ret"
echo "$e"
Run Code Online (Sandbox Code Playgroud)
有效并且对“返回值”没有任何限制。