環境を変更するコマンドの出力を変数に保存する

環境を変更するコマンドの出力を変数に保存する

環境を変更するコマンドの出力を変数に保存するにはどうすればよいですか?

私はbashシェルを使用しています。

以下のものがあると仮定します。

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

したがって、ここでは環境の変化はありません。

コマンドの出力(関数だけでなく)を変数に保存し、環境の変更を保存するにはどうすればよいですか?

$(...)新しいサブシェルが開き、それが問題であることはわかっていますが、回避策はありますか?

答え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、標準出力/入力ファイル記述子を配列に保存しますCOPROCpipe(2)、または参照ここまたはここ)。

ここでは、実行中のコプロセスに標準出力を向けて関数を実行しcat、次にreadそこから。catは入力を吐き出すだけなので、関数の出力が変数に格納されます。exec {COPROC[1]}>&-ファイル記述子を閉じるだけで、cat永遠に待機し続けることはありません。


read一度に 1 行しか取得できないことに注意してください。 を使用mapfileして行の配列を取得することも、ファイル記述子を使用することもできますが、別の方法で使用したい場合は、そのファイル記述子を使用するだけです。

exec {COPROC[1]}>&-現在のバージョンの Bash では動作しますが、以前の 4 シリーズのバージョンでは、まずファイル記述子を単純な変数に保存する必要があります: fd=${COPROC[1]}; exec {fd}>&-。変数が設定されていない場合は、標準出力が閉じられます。


Bashの3シリーズバージョンを使用している場合は、次の方法で同じ効果を得ることができます。mkfifoただし、その時点では実際のファイルを使用するよりもあまり優れていません。

答え2

すでに の本体が呼び出し元と同じシェルで実行され、やfなどの変数を変更できると想定している場合は、関数も設定するだけではいかがでしょうか。言い換えると、次のようになります。abc

$ 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

考えられる理由の 1 つは、出力変数が異なる場所で呼び出されるときに異なる名前 (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

おそらく、2 度評価する場合に正しく行うのが最も難しいのは、変数が引用符を壊してランダム コードを実行しないようにすることです。変数が評価される回数が増えるほど、難しくなります。ここではパラメーター展開が非常に役立ち、exportではなくを使用する方がevalはるかに安全です。

上記の例では、最初にnull文字列f()を割り当て、次に位置パラメータを設定して有効な変数名をテストします。両方のテストに合格しない場合は、 にメッセージが発行され、 のデフォルト値が に割り当てられます。ただし、テストに関係なく、 は常に または指定した名前に割り当てられ、 と、オプションで指定した名前には常に関数の出力値が割り当てられます。これを実証するために、次の小さなループを作成しました。$fout''stderrfout$fout_name$fout_namefout$foutfor

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'

関連情報