將修改環境的命令的輸出儲存到變數中

將修改環境的命令的輸出儲存到變數中

如何將修改環境的命令的輸出保存到變數中?

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

相關內容