如何将修改环境的命令的输出保存到变量中?
我正在使用 bash shell。
假设我有:
function f () { a=3; b=4 ; echo "`date`: $a $b"; }
现在,我可以使用命令来运行f
:
$ a=0; b=0; f; echo $a; echo $b; echo $c
Sat Jun 28 21:27:08 CEST 2014: 3 4
3
4
但我想将输出保存f
到变量c
,所以我尝试:
a=0; b=0; c=""; c=$(f); echo $a; echo $b; echo $c
但不幸的是,我有:
0
0
Sat Jun 28 21:28:03 CEST 2014: 3 4
所以我这里没有任何环境变化。
如何将命令(不仅仅是函数)的输出保存到变量并保存环境更改?
我知道这$(...)
会打开新的子 shell,这就是问题所在,但是是否可以采取一些解决方法?
答案1
如果您使用的是 Bash 4 或更高版本,则可以使用协进程:
function f () { a=3; b=4 ; echo "`date`: $a $b"; }
coproc cat
f >&${COPROC[1]}
exec {COPROC[1]}>&-
read c <&${COPROC[0]}
echo a $a
echo b $b
echo c $c
将输出
a 3
b 4
c Sun Jun 29 10:08:15 NZST 2014: 3 4
coproc
创建一个运行给定命令的新进程(此处为cat
)。它将 PID 保存到数组中COPROC_PID
,并将标准输出/输入文件描述符保存到数组中COPROC
(就像pipe(2)
,或参见这里或者这里)。
在这里,我们运行该函数,其标准输出指向正在运行的协进程cat
,然后read
从中。由于cat
只是将其输入吐出,因此我们将函数的输出放入变量中。exec {COPROC[1]}>&-
只是关闭文件描述符,这样cat
就不会永远等待。
请注意,read
一次只需要一行。您可以使用它mapfile
来获取行数组,或者仅使用文件描述符,但您想以不同的方式使用它。
exec {COPROC[1]}>&-
适用于当前版本的 Bash,但早期的 4 系列版本要求您首先将文件描述符保存到一个简单的变量中:fd=${COPROC[1]}; exec {fd}>&-
.如果您的变量未设置,它将关闭标准输出。
如果您使用的是 3 系列版本的 Bash,则可以使用以下命令获得相同的效果mkfifo
,但它并不比此时使用实际文件好多少。
答案2
如果您已经指望在与调用者相同的 shell 中执行主体f
,从而能够修改a
和等变量b
,为什么不直接设置该函数c
呢?换句话说:
$ function f () { a=3; b=4 ; c=$(echo "$(date): $a $b"); }
$ a=0; b=0; f; echo $a; echo $b; echo $c
3
4
Mon Jun 30 14:32:00 EDT 2014: 3 4
一个可能的原因可能是输出变量在不同位置调用时需要具有不同的名称(c1、c2 等),但您可以通过设置函数 c_TEMP 并让调用者执行此类操作来解决此问题c1=$c_TEMP
。
答案3
这是一个克鲁格,但尝试一下
f > c.file
c=$(cat c.file)
(并且可选地,rm c.file
)。
答案4
只需分配所有变量并同时写入输出即可。
f() { c= ; echo "${c:=$(date): $((a=3)) $((b=4))}" ; }
现在如果你这样做:
f ; echo "$a $b $c"
你的输出是:
Tue Jul 1 04:58:17 PDT 2014: 3 4
3 4 Tue Jul 1 04:58:17 PDT 2014: 3 4
请注意,这是完全 POSIX 可移植的代码。我最初设置c=
为''
空字符串,因为参数扩展将并发变量赋值+评估限制为仅数字(喜欢$((var=num))
)或来自 null 或不存在的值 - 或者,换句话说,您不能同时设置和如果该变量已被赋值,则将该变量评估为任意字符串。所以我只是在尝试之前确保它是空的。如果我在尝试分配它之前没有清空,则c
扩展只会返回旧值。
只是为了证明:
sh -c '
c=oldval a=1
echo ${c:=newval} $((a=a+a))
'
###OUTPUT###
oldval 2
newval
是不是分配给$c
内联,因为oldval
扩展为${word}
,而内联$((
算术=
分配))
总是发生。但如果$c
没有 oldval
并且为空或未设置...
sh -c '
c=oldval a=1
echo ${c:=newval} $((a=a+a))
c= a=$((a+a))
echo ${c:=newval} $((a=a+a))
'
###OUTPUT###
oldval 2
newval 8
...然后newval
立即分配并扩展为$c
.
所有其他方法都涉及某种形式的二次评估。例如,假设我希望将 的输出分配给在某一点和另一点f()
命名的变量。正如目前所写的,如果不在调用者的作用域中设置 var,这将无法工作。不过,另一种方式可能如下所示:name
var
f(){ fout_name= fout= set -- "${1##[0-9]*}" "${1%%*[![:alnum:]]*}"
(: ${2:?invalid or unspecified param - name set to fout}) || set --
export "${fout_name:=${1:-fout}}=${fout:=$(date): $((a=${a:-50}+1)) $((b=${b:-100}-4))}"
printf %s\\n "$fout"
}
f &&
printf %s\\n \
"$fout_name" \
"$fout" \
"$a" "$b"
我在下面提供了一个格式更好的示例,但是,如上所示,输出为:
sh: line 2: 2: invalid or unspecified param - name set to fout
Wed Jul 2 02:27:07 PDT 2014: 51 96
fout
Wed Jul 2 02:27:07 PDT 2014: 51 96
51
96
或者使用不同的$ENV
or 参数:
b=9 f myvar &&
printf %s\\n \
"$fout_name" \
"$fout" \
"$myvar" \
"$a" "$b"
###OUTPUT###
Tue Jul 1 19:56:42 PDT 2014: 52 5
myvar
Tue Jul 1 19:56:42 PDT 2014: 52 5
Tue Jul 1 19:56:42 PDT 2014: 52 5
52
5
当涉及到两次评估时,最棘手的事情可能是确保变量不会破坏引号并执行随机代码。一个变量被评估的次数越多,它就越难。参数扩展在这里有很大帮助,并且使用export
而不是eval
更安全。
在上面的示例中,f()
首先分配空$fout
字符串''
,然后设置位置参数来测试有效的变量名称。如果两个测试均未通过,则会向 发出一条消息stderr
,并将 的默认值fout
分配给$fout_name
。然而,无论测试如何,$fout_name
总是分配给fout
您指定的名称,$fout
并且(可选)您指定的名称始终分配给函数的输出值。为了证明这一点,我写了这个小循环for
:
for v in var '' "wr;\' ong"
do sleep 10 &&
a=${a:+$((a*2))} f "$v" || break
echo "${v:-'' #null}"
printf '#\t"$%s" = '"'%s'\n" \
a "$a" b "$b" \
fout_name "$fout_name" \
fout "$fout" \
'(eval '\''echo "$'\''"$fout_name"\")' \
"$(eval 'echo "$'"$fout_name"\")"
done
它使用变量名和参数扩展来解决一些问题。如果您有疑问,请尽管提问。那仅有的在此处已表示的函数中运行相同的几行。至少值得一提的是,$a
和$b
变量的行为有所不同,具体取决于它们是在调用时定义还是已经设置。尽管如此,for
除了格式化数据集并由 提供之外,它几乎什么也不做f()
。看一看:
###OUTPUT###
Wed Jul 2 02:50:17 PDT 2014: 51 96
var
# "$a" = '51'
# "$b" = '96'
# "$fout_name" = 'var'
# "$fout" = 'Wed Jul 2 02:50:17 PDT 2014: 51 96'
# "$(eval 'echo "$'"$fout_name"\")" = 'Wed Jul 2 02:50:17 PDT 2014: 51 96'
sh: line 2: 2: invalid or unspecified param - name set to fout
Wed Jul 2 02:50:27 PDT 2014: 103 92
'' #null
# "$a" = '103'
# "$b" = '92'
# "$fout_name" = 'fout'
# "$fout" = 'Wed Jul 2 02:50:27 PDT 2014: 103 92'
# "$(eval 'echo "$'"$fout_name"\")" = 'Wed Jul 2 02:50:27 PDT 2014: 103 92'
sh: line 2: 2: invalid or unspecified param - name set to fout
Wed Jul 2 02:50:37 PDT 2014: 207 88
wr;\' ong
# "$a" = '207'
# "$b" = '88'
# "$fout_name" = 'fout'
# "$fout" = 'Wed Jul 2 02:50:37 PDT 2014: 207 88'
# "$(eval 'echo "$'"$fout_name"\")" = 'Wed Jul 2 02:50:37 PDT 2014: 207 88'