Simpan output dari perintah yang mengubah lingkungan menjadi variabel

8

Bagaimana cara menyimpan output dari perintah yang mengubah lingkungan menjadi variabel?

Saya menggunakan bash shell.

Anggap saya punya:

function f () { a=3; b=4 ; echo "`date`: $a $b"; }

Dan sekarang, saya bisa menggunakan perintah untuk menjalankan f:

$ a=0; b=0; f; echo $a; echo $b; echo $c
Sat Jun 28 21:27:08 CEST 2014: 3 4
3
4

tapi saya ingin menyimpan output fke variabel c, jadi saya mencoba:

a=0; b=0; c=""; c=$(f); echo $a; echo $b; echo $c

tapi sayangnya, saya punya:

0
0
Sat Jun 28 21:28:03 CEST 2014: 3 4

jadi saya tidak punya perubahan lingkungan di sini.

Bagaimana cara menyimpan output dari perintah (tidak hanya berfungsi) ke variabel dan menyimpan perubahan lingkungan?

Saya tahu itu $(...)membuka subkulit baru dan itulah masalahnya, tetapi apakah mungkin untuk melakukan beberapa solusi?

faramir
sumber
Benar, jadi masalahnya adalah itu $adan $bmerupakan variabel lokal di ffungsi Anda . Anda bisa exportmelakukannya, tetapi itu tampak samar.
HalosGhost
1
@HalosGhost: Tidak, saya rasa tidak. Lihatlah contoh pertama: …; f; echo $a; …hasilnya 3digaungkan, begitu fjuga memodifikasi variabel shell (dan bukan hanya variabel lokalnya sendiri).
Scott

Jawaban:

2

Jika Anda menggunakan Bash 4 atau lambat, Anda dapat menggunakan 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

akan menampilkan

a 3
b 4
c Sun Jun 29 10:08:15 NZST 2014: 3 4

coprocmembuat proses baru menjalankan perintah yang diberikan (di sini, cat). Ini menyimpan PID ke dalam COPROC_PIDdan deskriptor file output / input standar ke dalam array COPROC(seperti pipe(2), atau lihat di sini atau di sini ).

Di sini kita menjalankan fungsinya dengan output standar yang menunjuk pada coprocess kita yang berjalan cat, dan kemudian readdarinya. Karena cathanya meludahkan inputnya kembali, kita mendapatkan output fungsi ke dalam variabel kita. exec {COPROC[1]}>&-tutup saja deskriptor file agar cattidak terus menunggu selamanya.


Perhatikan bahwa readhanya perlu satu baris setiap kali. Anda dapat menggunakan mapfileuntuk mendapatkan array baris, atau cukup gunakan deskriptor file namun Anda ingin menggunakannya dengan cara yang berbeda.

exec {COPROC[1]}>&-karya dalam versi saat ini dari Bash, namun versi sebelumnya 4-series mengharuskan Anda untuk menyimpan file descriptor ke dalam variabel sederhana pertama: fd=${COPROC[1]}; exec {fd}>&-. Jika variabel Anda tidak disetel, itu akan menutup output standar.


Jika Anda menggunakan Bash versi 3-seri, Anda bisa mendapatkan efek yang sama mkfifo, tetapi itu tidak jauh lebih baik daripada menggunakan file aktual pada saat itu.

Michael Homer
sumber
Saya hanya bisa menggunakan bash 3.1. Pipa-pipa itu sulit digunakan. Apakah mktemp dan merupakan satu-satunya pilihan? Bukankah di sini ada sintaks atau opsi khusus untuk menjalankan perintah dan mendapatkan output tetapi dengan perubahan lingkungan?
faramir
Anda dapat menggunakan mkfifo untuk membuat pipa bernama sebagai pengganti coproc, yang sebenarnya tidak melibatkan penulisan data ke disk. Dengan itu Anda akan mengarahkan output ke fifo dan masukan dari itu alih-alih proses. Kalau tidak, tidak.
Michael Homer
+1 untuk mengadaptasi jawaban saya untuk tidak menggunakan file. Tetapi, dalam pengujian saya, exec {COPROC[1]}>&-perintah (setidaknya kadang-kadang) segera menghentikan proses , sehingga hasilnya tidak lagi tersedia untuk dibaca. coproc { cat; sleep 1; }tampaknya bekerja lebih baik. (Anda mungkin perlu menambah 1jika Anda membaca lebih dari satu baris.)
Scott
1
Ada arti konvensional, yang dipahami dengan baik dari "file aktual" yang digunakan di sini yang sengaja Anda abaikan.
Michael Homer
1
"File aktual" berarti file pada disk , sebagaimana dipahami oleh semua orang di sini kecuali, diduga, Anda. Tidak ada yang pernah menyiratkan atau mengizinkan pembaca yang masuk akal untuk menyimpulkan bahwa salah satu dari mereka sama dengan argumen atau variabel lingkungan. Saya tidak tertarik melanjutkan diskusi ini.
Michael Homer
2

Jika Anda sudah mengandalkan tubuh fmengeksekusi di shell yang sama dengan pemanggil, dan dengan demikian dapat memodifikasi variabel seperti adan b, mengapa tidak membuat fungsi hanya diatur cjuga? Dengan kata lain:

$ 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

Salah satu alasan yang mungkin adalah bahwa variabel output perlu memiliki nama yang berbeda (c1, c2, dll) ketika dipanggil di tempat yang berbeda, tetapi Anda bisa mengatasinya dengan mengatur fungsi c_TEMP dan meminta penelepon melakukannya c1=$c_TEMPdll.

godlygeek
sumber
1

Ini kluge, tapi cobalah

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

(dan, secara opsional, rm c.file).

Scott
sumber
Terima kasih. Itu adalah jawaban yang sederhana, dan berhasil, tetapi memiliki beberapa kelemahan. Saya tahu bahwa saya dapat menggunakan mktemp, tetapi saya ingin tidak membuat file tambahan.
faramir
1

Cukup tetapkan semua variabel dan tulis output pada saat yang sama.

f() { c= ; echo "${c:=$(date): $((a=3)) $((b=4))}" ; }

Sekarang jika Anda melakukannya:

f ; echo "$a $b $c"

Output Anda adalah:

Tue Jul  1 04:58:17 PDT 2014: 3 4
3 4 Tue Jul  1 04:58:17 PDT 2014: 3 4

Perhatikan bahwa ini sepenuhnya kode portabel POSIX. Saya awalnya ditetapkan c=ke ''string nol karena ekspansi parameter membatasi penugasan variabel bersamaan + evaluasi untuk hanya numerik (seperti $((var=num))) atau dari nilai nol atau tidak ada - atau, dengan kata lain, Anda tidak dapat secara bersamaan mengatur dan mengevaluasi variabel ke string yang arbitrer jika variabel itu sudah diberi nilai. Jadi saya hanya memastikan bahwa itu kosong sebelum mencoba. Jika saya tidak mengosongkan csebelum mencoba untuk menetapkan ekspansi itu hanya akan mengembalikan nilai lama.

Hanya untuk menunjukkan:

sh -c '
    c=oldval a=1
    echo ${c:=newval} $((a=a+a))
'
###OUTPUT###
oldval 2

newvaladalah tidak ditugaskan untuk $cinline karena oldvaldiperluas ke ${word}, sedangkan inline $((aritmatika =tugas ))selalu terjadi. Tetapi jika $c tidak ada oldval dan kosong atau tidak disetel ...

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

... kemudian newvalditugaskan dan diperluas ke $c.

Semua cara lain untuk melakukan ini melibatkan beberapa bentuk evaluasi sekunder. Sebagai contoh, katakanlah saya ingin menetapkan output f()ke variabel bernama namepada satu titik dan vardi titik lain. Seperti yang ditulis saat ini, ini tidak akan berfungsi tanpa mengatur var dalam ruang lingkup penelepon. Namun, cara yang berbeda dapat terlihat seperti ini:

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"

Saya telah memberikan contoh yang diformat lebih baik di bawah ini, tetapi, disebut seperti di atas, keluarannya adalah:

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

Atau dengan $ENVargumen atau perbedaan :

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

Mungkin hal paling sulit untuk dilakukan dengan benar ketika mengevaluasi dua kali adalah memastikan bahwa variabel tidak memecah tanda kutip dan mengeksekusi kode acak. Semakin banyak variabel dievaluasi semakin sulit. Ekspansi parameter sangat membantu di sini, dan menggunakan exportsebagai lawan evaljauh lebih aman.

Dalam contoh di atas f()pertama memberikan $foutyang ''nol tali dan kemudian menetapkan params posisi ke tes untuk nama variabel yang valid. Jika kedua pengujian tidak lulus pesan dipancarkan ke stderrdan nilai default foutditugaskan untuk $fout_name. Terlepas dari tes, bagaimanapun, $fout_nameselalu ditugaskan untuk salah satu foutatau nama yang Anda tentukan $foutdan, dan opsional, nama yang Anda tentukan selalu diberi nilai output dari fungsi. Untuk menunjukkan ini, saya menulis forlingkaran kecil ini :

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

Memainkan beberapa dengan nama variabel dan ekspansi parameter. Jika Anda memiliki pertanyaan, tanyakan saja. Itu hanya menjalankan beberapa baris yang sama dalam fungsi yang sudah diwakili di sini. Perlu disebutkan setidaknya bahwa variabel $adan $bberperilaku berbeda tergantung pada apakah mereka didefinisikan pada doa, atau sudah ditetapkan. Namun, forhampir tidak melakukan apa-apa selain memformat kumpulan data dan disediakan oleh f(). Lihat:

###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'
mikeserv
sumber