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


8

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

我正在使用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到variable 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

所以我这里没有任何环境变化。

如何将命令(不仅是功能)的输出保存为变量并保存环境更改?

我知道这会$(...)打开新的子外壳,这是问题所在,但是有可能做一些解决方法吗?


对,所以这个问题是$a$b在本地变量f功能。您可以使用export它们,但这似乎很粗略。
HalosGhost 2014年

1
@HalosGhost:不,我不这么认为。看第一个例子:…; f; echo $a; …导致3回显,因此f修改shell变量(而不仅仅是它自己的局部变量)也是如此。
斯科特(Scott)

Answers:


2

如果您使用的是Bash 4或更高版本,则可以使用coprocesses

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}>&-。如果您的变量未设置,它将关闭标准输出。


如果您使用的是Bash的3系列版本,则可以使用来获得相同的效果mkfifo,但这并不比此时使用实际文件好多少。


我只能使用bash 3.1。管道很难使用。请问mktemp和是唯一的选择吗?这里不是使用特殊的语法或选项来运行命令并获取输出,而是随着环境的变化而变化吗?
faramir

您可以使用mkfifo来创建一个命名管道来代替coproc,这实际上并不涉及将数据写入磁盘。这样,您将输出重定向到fifo并从中输入,而不是协同处理。否则没有。
Michael Homer 2014年

+1使我的答案不使用文件。但是,在我的测试中,exec {COPROC[1]}>&-命令(至少有时)立即终止了协进程,因此不再可以读取其输出。coproc { cat; sleep 1; }似乎效果更好。(1如果您要阅读多行内容,则可能需要增加。)
Scott

1
在这里,有一个常规的,易于理解的“实际文件”的含义,您可能会忽略。
Michael Homer

1
“实际文件”是指磁盘上的文件,据此每个人都可以理解,但据称是您自己。从来没有人暗示或允许理性的读者推断它们两者都与参数或环境变量相同。我对继续进行此讨论不感兴趣。
Michael Homer 2014年

2

如果您已经打算f在与调用方相同的shell 中执行程序,因此能够修改像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


1

这是克鲁格,但尝试

f > c.file
c=$(cat c.file)

(以及(可选rm c.file))。


谢谢。这是简单的答案,并且可以工作,但是有一些缺点。我知道可以使用mktemp,但是我不想创建其他文件。
faramir

1

只需分配所有变量并同时写入输出即可。

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=''null字符串是因为参数扩展将并发变量赋值+求值限制为仅数字(如$((var=num))或空值或不存在的值-换句话说,您无法同时将变量设置求值为任意字符串如果该变量已经分配了值。因此,在尝试之前,请确保它为空。如果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()给一个name在另一点命名的变量var。如当前所写,如果没有在调用者的作用域中设置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或参数:

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''空字符串,然后设置位置PARAMS到测试为有效的变量名。如果两个测试均未通过,则会向发出一条消息,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'
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.