将修改环境的命令的输出保存到变量中

将修改环境的命令的输出保存到变量中

如何将修改环境的命令的输出保存到变量中?

我正在使用 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,这将无法工作。不过,另一种方式可能如下所示:namevar

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

或者使用不同的$ENVor 参数:

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'

相关内容