Saya sedang mengerjakan ini:
GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu)
Saya memiliki skrip seperti di bawah ini:
#!/bin/bash
e=2
function test1() {
e=4
echo "hello"
}
test1
echo "$e"
Yang mengembalikan:
hello
4
Tetapi jika saya menetapkan hasil fungsi ke variabel, variabel global e
tidak diubah:
#!/bin/bash
e=2
function test1() {
e=4
echo "hello"
}
ret=$(test1)
echo "$ret"
echo "$e"
Pengembalian:
hello
2
Saya pernah mendengar tentang penggunaan eval dalam kasus ini, jadi saya melakukan ini di test1
:
eval 'e=4'
Tapi hasilnya sama.
Bisakah Anda menjelaskan mengapa itu tidak diubah? Bagaimana saya bisa menyimpan gema test1
fungsi ret
dan memodifikasi variabel global juga?
bash
variables
global-variables
eval
harrison4
sumber
sumber
Jawaban:
Saat Anda menggunakan substitusi perintah (yaitu
$(...)
konstruksi), Anda membuat subkulit. Subkulit mewarisi variabel dari cangkang induknya, tetapi ini hanya bekerja satu cara - subkulit tidak dapat mengubah lingkungan cangkang induknya. Variabel Andae
disetel di dalam subkulit, tetapi bukan shell induk. Ada dua cara untuk meneruskan nilai dari subkulit ke induknya. Pertama, Anda dapat mengeluarkan sesuatu ke stdout, lalu menangkapnya dengan substitusi perintah:Memberikan:
Untuk nilai numerik 0-255, Anda dapat menggunakan
return
untuk meneruskan nomor sebagai status keluar:Memberikan:
sumber
setarray() { declare -ag "$1=(a b c)"; }
Ringkasan
Contoh Anda dapat dimodifikasi sebagai berikut untuk mengarsipkan efek yang diinginkan:
mencetak sesuai keinginan:
Perhatikan bahwa solusi ini:
e=1000
juga.$?
jika Anda membutuhkan$?
Satu-satunya efek samping yang buruk adalah:
bash
._
)_capture
hanya mengganti semua kejadian dari3
dengan nomor lain (yang lebih tinggi).Berikut ini (yang cukup panjang, maaf untuk itu) mudah-mudahan menjelaskan, bagaimana cara menambahkan resep ini ke skrip lain juga.
Masalah
keluaran
sedangkan keluaran yang diinginkan adalah
Penyebab masalahnya
Variabel shell (atau secara umum, lingkungan) diteruskan dari proses orang tua ke proses anak, tetapi tidak sebaliknya.
Jika Anda melakukan pengambilan keluaran, ini biasanya dijalankan dalam subkulit, jadi sulit untuk meneruskan kembali variabel.
Beberapa bahkan memberi tahu Anda, bahwa tidak mungkin untuk memperbaikinya. Ini salah, tetapi sudah lama diketahui sulit untuk memecahkan masalah.
Ada beberapa cara untuk menyelesaikannya dengan sebaik-baiknya, ini tergantung kebutuhan Anda.
Berikut adalah panduan langkah demi langkah tentang cara melakukannya.
Meneruskan kembali variabel ke dalam cangkang induk
Ada cara untuk meneruskan kembali variabel ke cangkang induk. Bagaimanapun ini adalah jalan yang berbahaya, karena ini berguna
eval
. Jika dilakukan dengan tidak benar, Anda mempertaruhkan banyak hal jahat. Tetapi jika dilakukan dengan benar, ini sangat aman, asalkan tidak ada bug yang masukbash
.cetakan
Perhatikan bahwa ini juga berfungsi untuk hal-hal berbahaya:
cetakan
Hal ini disebabkan
printf '%q'
, yang mengutip semuanya, sehingga Anda dapat menggunakannya kembali dalam konteks shell dengan aman.Tapi ini menyebalkan di ..
Ini tidak hanya terlihat jelek, tapi juga banyak mengetik, jadi rawan kesalahan. Hanya satu kesalahan dan Anda dikutuk, bukan?
Ya, kami berada di level shell, jadi Anda bisa meningkatkannya. Pikirkan tentang antarmuka yang ingin Anda lihat, dan kemudian Anda dapat menerapkannya.
Augment, bagaimana shell memproses sesuatu
Mari mundur selangkah dan pikirkan tentang beberapa API yang memungkinkan kita mengekspresikan dengan mudah, apa yang ingin kita lakukan.
Nah, apa yang ingin kita lakukan dengan
d()
fungsinya?Kami ingin menangkap output menjadi variabel. Oke, mari kita terapkan API untuk hal ini:
Sekarang, alih-alih menulis
kita bisa menulis
Nah, ini sepertinya kita tidak banyak berubah, karena, sekali lagi, variabel tidak dikirimkan kembali
d
ke dalam shell induk, dan kita perlu mengetik sedikit lagi.Namun sekarang kita bisa menggunakan kekuatan penuh dari shell padanya, karena itu dibungkus dengan baik dalam sebuah fungsi.
Pikirkan tentang antarmuka yang mudah digunakan kembali
Kedua, kita ingin KERING (Don't Repeat Yourself). Jadi kami pasti tidak ingin mengetik sesuatu seperti
Di
x
sini tidak hanya berlebihan, itu juga rawan kesalahan untuk selalu berulang dalam konteks yang benar. Bagaimana jika Anda menggunakannya 1000 kali dalam skrip dan kemudian menambahkan variabel? Anda pasti tidak ingin mengubah semua 1000 lokasi tempat panggilan ked
terlibat.Jadi tinggalkan
x
saja, jadi kita bisa menulis:keluaran
Ini sudah terlihat sangat bagus. (Tapi masih ada
local -n
yang tidak bekerja pada kesamaanbash
3.x)Hindari berubah
d()
Solusi terakhir memiliki beberapa kekurangan besar:
d()
perlu diubahxcapture
untuk meneruskan output.output
, jadi kita tidak akan pernah bisa melewatkan yang ini kembali._passback
Bisakah kita menyingkirkan ini juga?
Tentu saja kita bisa! Kita berada dalam cangkang, jadi ada semua yang kita butuhkan untuk menyelesaikannya.
Jika Anda melihat lebih dekat ke panggilan tersebut,
eval
Anda dapat melihat, bahwa kami memiliki kontrol 100% di lokasi ini. "Inside" theeval
we are in a subshell, so we can do everything we want tanpa takut melakukan sesuatu yang buruk pada parental shell.Ya, bagus, jadi mari tambahkan pembungkus lain, sekarang langsung di dalam
eval
:cetakan
Namun, ini, sekali lagi, memiliki beberapa kelemahan utama:
!DO NOT USE!
spidol yang ada, karena ada kondisi lomba yang sangat buruk dalam hal ini, yang Anda tidak dapat melihat dengan mudah:>(printf ..)
adalah pekerjaan latar belakang. Jadi mungkin masih mengeksekusi saat_passback x
sedang berjalan.sleep 1;
sebelumprintf
atau_passback
._xcapture a d; echo
lalu keluaranx
ataua
pertama, masing-masing._passback x
tidak harus menjadi bagian dari_xcapture
, karena ini membuat sulit untuk menggunakan kembali resep itu.$(cat)
), tetapi karena solusi ini!DO NOT USE!
saya mengambil rute terpendek.Namun, ini menunjukkan, bahwa kita dapat melakukannya, tanpa modifikasi
d()
(dan tanpalocal -n
)!Harap dicatat bahwa kami tidak perlu
_xcapture
sama sekali, karena kami dapat menulis semuanya langsung dieval
.Namun melakukan ini biasanya tidak terlalu mudah dibaca. Dan jika Anda kembali ke skrip Anda dalam beberapa tahun, Anda mungkin ingin dapat membacanya lagi tanpa banyak kesulitan.
Perbaiki balapan
Sekarang mari perbaiki kondisi balapan.
Triknya bisa menunggu sampai
printf
ditutup STDOUT-nya, dan kemudian keluaranx
.Ada banyak cara untuk mengarsipkannya:
Mengikuti jalur terakhir akan terlihat seperti (perhatikan bahwa ini melakukan yang
printf
terakhir karena ini bekerja lebih baik di sini):keluaran
Mengapa ini benar?
_passback x
langsung berbicara dengan STDOUT.>&3
.$("${@:2}" 3<&-; _passback x >&3)
selesai setelah_passback
, ketika subkulit menutup stdout.printf
tidak bisa terjadi sebelum_passback
, berapa lama pun_passback
waktunya.printf
perintah tidak dijalankan sebelum baris perintah lengkap dipasang, jadi kita tidak dapat melihat artefak dariprintf
, secara independen bagaimanaprintf
diimplementasikan.Oleh karena itu pertama-tama
_passback
dieksekusi, laluprintf
.Ini menyelesaikan perlombaan, mengorbankan satu file deskriptor tetap 3. Anda dapat, tentu saja, memilih deskriptor file lain dalam kasus, bahwa FD3 tidak gratis di shellscript Anda.
Harap perhatikan juga
3<&-
yang melindungi FD3 untuk diteruskan ke fungsi.Jadikan lebih umum
_capture
mengandung bagian-bagian, yang dimilikid()
, yang buruk, dari perspektif dapat digunakan kembali. Bagaimana mengatasi ini?Nah, lakukan dengan cara yang berbeda dengan memperkenalkan satu hal lagi, fungsi tambahan, yang harus mengembalikan hal yang benar, yang dinamai sesuai fungsi asli dengan
_
lampiran.Fungsi ini dipanggil setelah fungsi sebenarnya, dan dapat menambah banyak hal. Dengan cara ini, ini dapat dibaca sebagai beberapa anotasi, sehingga sangat mudah dibaca:
masih mencetak
Izinkan akses ke kode kembali
Hanya ada sedikit yang hilang:
v=$(fn)
set$?
ke apa yangfn
dikembalikan. Jadi, Anda mungkin menginginkan ini juga. Perlu beberapa penyesuaian yang lebih besar, meskipun:cetakan
Masih banyak ruang untuk perbaikan
_passback()
bisa dieliminasi denganpassback() { set -- "$@" "$?"; while [ 1 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; return $1; }
_capture()
bisa dihilangkan dengancapture() { eval "$({ out="$("${@:2}" 3<&-; "$2_" >&3)"; ret=$?; printf "%q=%q;" "$1" "$out"; } 3>&1; echo "(exit $ret)")"; }
Solusi mencemari deskriptor file (di sini 3) dengan menggunakannya secara internal. Anda perlu mengingatnya jika kebetulan lulus FD.
Perhatikan bahwa
bash
4.1 dan yang lebih baru{fd}
harus menggunakan beberapa FD yang tidak digunakan.(Mungkin saya akan menambahkan solusi di sini ketika saya datang.)
Perhatikan bahwa inilah mengapa saya menggunakan untuk meletakkannya dalam fungsi terpisah seperti
_capture
, karena memasukkan semua ini ke dalam satu baris dimungkinkan, tetapi membuatnya semakin sulit untuk dibaca dan dipahamiMungkin Anda ingin menangkap STDERR dari fungsi yang dipanggil juga. Atau Anda bahkan ingin meneruskan masuk dan keluar lebih dari satu penulis naskah dari dan ke variabel.
Saya belum memiliki solusi, namun berikut adalah cara untuk menangkap lebih dari satu FD , jadi kita mungkin dapat mengirimkan kembali variabel dengan cara ini juga.
Juga jangan lupa:
Ini harus memanggil fungsi shell, bukan perintah eksternal.
Kata-kata terakhir
Ini bukan satu-satunya solusi yang mungkin. Ini adalah salah satu contoh solusi.
Seperti biasa, Anda memiliki banyak cara untuk mengekspresikan sesuatu di shell. Jadi jangan ragu untuk meningkatkan dan menemukan sesuatu yang lebih baik.
Solusi yang disajikan di sini masih jauh dari sempurna:
bash
, jadi mungkin sulit untuk melakukan port ke shell lain.Namun menurut saya ini cukup mudah digunakan:
sumber
Mungkin Anda dapat menggunakan file, menulis ke file di dalam fungsi, membaca dari file setelahnya. Saya telah berubah
e
menjadi array. Dalam contoh ini, kosong digunakan sebagai pemisah saat membaca kembali array.Keluaran:
sumber
Apa yang Anda lakukan, Anda menjalankan test1
$(test1)
di sub-shell (anak shell) dan anak shell tidak bisa memodifikasi apa pun di induk .
Anda dapat menemukannya di manual bash
Silakan Periksa: Hal-hal menghasilkan subkulit di sini
sumber
Saya memiliki masalah yang sama, ketika saya ingin secara otomatis menghapus file temp yang telah saya buat. Solusi yang saya dapatkan bukanlah menggunakan substitusi perintah, melainkan meneruskan nama variabel, yang seharusnya mengambil hasil akhir, ke dalam fungsi. Misalnya
Jadi, dalam kasus Anda itu adalah:
Bekerja dan tidak memiliki batasan pada "nilai kembali".
sumber
Itu karena substitusi perintah dilakukan di subkulit, jadi sementara subkulit mewarisi variabel, perubahan pada mereka hilang ketika subkulit berakhir.
Referensi :
sumber
Solusi untuk masalah ini, tanpa harus memperkenalkan fungsi yang kompleks dan sangat memodifikasi fungsi aslinya, adalah dengan menyimpan nilai dalam file sementara dan membaca / menulisnya jika diperlukan.
Pendekatan ini sangat membantu saya ketika saya harus mengejek fungsi bash yang dipanggil beberapa kali dalam kasus uji kelelawar.
Misalnya, Anda dapat memiliki:
Kekurangannya adalah Anda mungkin memerlukan beberapa file temporer untuk variabel yang berbeda. Dan juga Anda mungkin perlu mengeluarkan
sync
perintah untuk mempertahankan konten di disk antara satu operasi tulis dan baca.sumber
Anda selalu dapat menggunakan alias:
sumber