Apakah ada cara untuk membuat serial sebuah variabel shell? Misalkan saya memiliki variabel $VAR
, dan saya ingin dapat menyimpannya ke file atau apa pun, dan kemudian membacanya kembali nanti untuk mendapatkan nilai yang sama kembali?
Apakah ada cara portabel untuk melakukan ini? (Kurasa tidak)
Apakah ada cara untuk melakukannya di bash atau zsh?
Jawaban:
Peringatan: Dengan salah satu solusi ini, Anda harus menyadari bahwa Anda memercayai integritas file data agar aman karena akan dieksekusi sebagai kode shell dalam skrip Anda. Mengamankan mereka sangat penting untuk keamanan skrip Anda!
Implementasi inline sederhana untuk membuat serial satu atau lebih variabel
Ya, dalam bash dan zsh Anda bisa membuat serial konten dari suatu variabel dengan cara yang mudah diambil menggunakan
typeset
builtin dan-p
argumen. Format output sedemikian rupa sehingga Anda dapat dengan mudahsource
output untuk mendapatkan barang-barang Anda kembali.Anda bisa mendapatkan barang-barang Anda kembali seperti ini nanti di skrip Anda atau di skrip lain sekaligus:
Ini akan bekerja untuk bash, zsh dan ksh termasuk mengirimkan data antara shell yang berbeda. Bash akan menerjemahkan ini ke
declare
fungsi bawaannya sementara zsh mengimplementasikan ini dengantypeset
tetapi sebagai bash memiliki alias untuk ini berfungsi baik cara yang kami gunakan ditypeset
sini untuk kompatibilitas ksh.Implementasi umum yang lebih kompleks menggunakan fungsi
Implementasi di atas sangat sederhana, tetapi jika Anda sering memanggilnya, Anda mungkin ingin memberi diri Anda fungsi utilitas untuk membuatnya lebih mudah. Selain itu jika Anda pernah mencoba untuk memasukkan fungsi-fungsi khusus di dalam Anda akan mengalami masalah dengan pelingkupan variabel. Versi ini harus menghilangkan masalah-masalah itu.
Catatan untuk semua ini, untuk menjaga kompatibilitas silang bash / zsh, kami akan memperbaiki kedua kasus
typeset
dandeclare
sehingga kode harus bekerja di salah satu atau kedua shell. Ini menambahkan sejumlah besar dan kekacauan yang bisa dihilangkan jika Anda hanya melakukan ini untuk satu shell atau yang lain.Masalah utama dengan menggunakan fungsi untuk ini (atau termasuk kode dalam fungsi lain) adalah bahwa
typeset
fungsi menghasilkan kode itu, ketika bersumber kembali ke dalam skrip dari dalam suatu fungsi, default untuk membuat variabel lokal daripada yang global.Ini dapat diperbaiki dengan satu dari beberapa peretasan. Upaya awal saya untuk memperbaikinya adalah mem-parsing output dari proses serialisasi
sed
hingga menambahkan-g
flag sehingga kode yang dibuat mendefinisikan variabel global ketika bersumber kembali.Perhatikan bahwa
sed
ekspresi funky hanya untuk mencocokkan dengan kemunculan pertama 'typeset' atau 'menyatakan' dan menambahkan-g
sebagai argumen pertama. Hal ini diperlukan untuk hanya mencocokkan kejadian pertama karena, seperti Stéphane Chazelas menunjukkan dengan benar dalam komentar, jika tidak, itu juga akan cocok dengan kasus di mana string serial berisi baris baru literal diikuti dengan kata menyatakan atau mengeset.Selain mengoreksi awal parsing saya kecerobohan , Stéphane juga menyarankan cara yang kurang rapuh untuk hack ini yang tidak hanya langkah samping masalah dengan parsing string tetapi bisa menjadi hook berguna untuk menambah fungsionalitas tambahan dengan menggunakan fungsi pembungkus untuk mendefinisikan tindakan diambil saat sumber data kembali masuk. Ini mengasumsikan Anda tidak memainkan game lain dengan perintah mendeklarasikan atau mengeset, tetapi teknik ini akan lebih mudah untuk diterapkan dalam situasi di mana Anda memasukkan fungsi ini sebagai bagian dari fungsi lain dari Anda sendiri atau Anda tidak mengendalikan data yang sedang ditulis dan apakah
-g
bendera itu ditambahkan atau tidak . Hal serupa juga bisa dilakukan dengan alias, lihat jawaban Gilles untuk implementasi.Untuk menjadikan hasilnya lebih bermanfaat, kita dapat mengulangi beberapa variabel yang diteruskan ke fungsi kita dengan mengasumsikan bahwa setiap kata dalam array argumen adalah nama variabel. Hasilnya menjadi seperti ini:
Dengan salah satu solusi, penggunaan akan terlihat seperti ini:
sumber
declare
adalahbash
setaraksh
dengantypeset
.bash
,zsh
juga mendukungtypeset
sehingga dalam hal itu,typeset
lebih portabel.export -p
adalah POSIX, tetapi ia tidak mengambil argumen apa pun dan hasilnya tergantung pada shell (meskipun itu ditentukan dengan baik untuk shell POSIX, jadi misalnya ketika bash atau ksh disebut sebagaish
). Ingatlah untuk mengutip variabel Anda; menggunakan operator split + glob di sini tidak masuk akal.-E
hanya ditemukan di beberapa BSDsed
. Nilai variabel dapat berisi karakter baris baru, sehinggased 's/^.../.../'
tidak dijamin berfungsi dengan benar.a=$'foo\ndeclare bar' bash -c 'declare -p a'
untuk menginstal akan menampilkan garis yang dimulai dengandeclare
. Mungkin lebih baik dilakukandeclare() { builtin declare -g "$@"; }
sebelum meneleponsource
(dan tidakshopt -s expandalias
ketika tidak interaktif. Dengan fungsi, Anda juga bisa meningkatkandeclare
pembungkus sehingga hanya mengembalikan variabel yang Anda tentukan.Gunakan pengalihan, substitusi perintah, dan ekspansi parameter. Kutipan ganda diperlukan untuk mempertahankan spasi dan karakter khusus. Trailing
x
menyimpan baris baru trailing yang akan dihapus dalam substitusi perintah.sumber
Serialisasi semua - POSIX
Di setiap shell POSIX, Anda bisa membuat serial semua variabel lingkungan dengan
export -p
. Ini tidak termasuk variabel shell yang tidak diekspor. Outputnya dikutip dengan benar sehingga Anda dapat membacanya kembali di shell yang sama dan mendapatkan nilai variabel yang persis sama. Output mungkin tidak dapat dibaca di shell lain, misalnya ksh menggunakan$'…'
sintaks non-POSIX .Serialisasi beberapa atau semua - ksh, bash, zsh
Ksh (baik pdksh / mksh dan ATT ksh), bash dan zsh menyediakan fasilitas yang lebih baik dengan
typeset
builtin.typeset -p
mencetak semua variabel yang ditentukan dan nilainya (zsh menghilangkan nilai variabel yang telah disembunyikantypeset -H
). Outputnya berisi deklarasi yang tepat sehingga variabel lingkungan diekspor saat dibaca kembali (tetapi jika variabel sudah diekspor saat dibaca kembali, itu tidak akan diekspor), sehingga array dibaca kembali sebagai array, dll. Di sini juga, output dikutip dengan benar tetapi hanya dijamin dapat dibaca dalam shell yang sama. Anda dapat melewati serangkaian variabel untuk membuat serial pada baris perintah; Jika Anda tidak lulus variabel apa pun maka semua serial.Dalam bash dan zsh, mengembalikan tidak dapat dilakukan dari suatu fungsi karena
typeset
pernyataan di dalam suatu fungsi dibatasi untuk fungsi itu. Anda perlu menjalankan. ./some_vars
dalam konteks di mana Anda ingin menggunakan nilai-nilai variabel, berhati-hatilah bahwa variabel yang global ketika diekspor akan dideklarasikan ulang sebagai global. Jika Anda ingin membaca kembali nilai-nilai dalam suatu fungsi dan mengekspornya, Anda dapat mendeklarasikan alias atau fungsi sementara. Dalam zsh:Di bash (yang menggunakan
declare
daripadatypeset
):Di ksh,
typeset
deklarasikan variabel lokal dalam fungsi yang didefinisikanfunction function_name { … }
dan variabel global dalam fungsi yang didefinisikanfunction_name () { … }
.Serialize some - POSIX
Jika Anda ingin lebih banyak kontrol, Anda dapat mengekspor konten variabel secara manual. Untuk mencetak konten variabel secara tepat ke dalam file, gunakan
printf
builtin (echo
memiliki beberapa kasus khusus sepertiecho -n
pada beberapa shell dan menambahkan baris baru):Anda dapat membaca ini kembali dengan
$(cat VAR.content)
, kecuali bahwa substitusi perintah menanggalkan baris baru. Untuk menghindari kerutan ini, atur agar keluaran tidak berakhir dengan baris baru.Jika Anda ingin mencetak beberapa variabel, Anda dapat mengutipnya dengan tanda kutip tunggal, dan mengganti semua tanda kutip tunggal yang disematkan
'\''
. Bentuk kutipan ini dapat dibaca kembali ke shell gaya Bourne / POSIX apa pun. Cuplikan berikut ini berfungsi di shell POSIX apa pun. Ini hanya berfungsi untuk variabel string (dan variabel numerik di shell yang memilikinya, meskipun mereka akan dibaca kembali sebagai string), itu tidak mencoba untuk berurusan dengan variabel array di shell yang memilikinya.Berikut ini pendekatan lain yang tidak membayar subproses tetapi lebih berat pada manipulasi string.
Perhatikan bahwa pada shell yang memungkinkan variabel read-only, Anda akan mendapatkan kesalahan jika Anda mencoba membaca kembali variabel yang read-only.
sumber
$PWD
dan$_
- silakan lihat komentar Anda sendiri di bawah ini.typeset
alias untuktypeset -g
?Banyak terima kasih kepada @ stéphane-chazelas yang menunjukkan semua masalah dengan upaya saya sebelumnya, sekarang ini tampaknya bekerja untuk serialise array ke stdout atau ke dalam variabel.
Teknik ini tidak mem-parsing input (tidak seperti
declare -a
/declare -p
) dan aman terhadap penyisipan metakarakter berbahaya dalam teks serial.Catatan: baris baru tidak luput, karena
read
menghapus\<newlines>
pasangan karakter, jadi-d ...
alih-alih harus diteruskan untuk membaca, dan kemudian baris dipertahankan.Semua ini dikelola di
unserialise
fungsinya.Dua karakter ajaib digunakan, pemisah bidang dan pemisah rekaman (sehingga beberapa array dapat diserialisasi ke aliran yang sama).
Karakter-karakter ini dapat didefinisikan sebagai
FS
danRS
tetapi tidak dapat didefinisikan sebagainewline
karakter karena baris baru yang dihapus dihapus olehread
.Karakter pelarian haruslah
\
garis miring terbalik, karena itulah yang digunakan olehread
untuk menghindari karakter yang dikenali sebagaiIFS
karakter.serialise
akan bersambung"$@"
ke stdout,serialise_to
akan bersambung ke varable yang bernama in$1
dan tidak terhubung dengan:
atau
misalnya
(tanpa baris baru)
baca kembali:
atau
Bash
read
menghormati karakter pelariannya\
(kecuali Anda melewati flag -r) untuk menghapus makna khusus karakter seperti untuk pemisahan bidang input atau pembatas garis.Jika Anda ingin membuat serial array bukan hanya daftar argumen, maka hanya lulus array Anda sebagai daftar argumen:
Anda dapat menggunakan
unserialise
dalam satu lingkaran seperti yang Anda lakukanread
karena itu hanya pembacaan terbungkus - tetapi ingat bahwa alirannya tidak dipisahkan dengan baris baru:sumber
bash
danzsh
menjadikannya sebagai$'\xxx'
. Coba denganbash -c $'printf "%q\n" "\t"'
ataubash -c $'printf "%q\n" "\u0378"'
$IFS
tidak dimodifikasi dan sekarang gagal mengembalikan elemen array kosong dengan benar. Bahkan, akan lebih masuk akal untuk menggunakan nilai IFS yang berbeda, dan gunakan-d ''
untuk menghindari keharusan keluar dari baris baru. Sebagai contoh, gunakan:
sebagai pemisah bidang dan hanya lolos itu dan backslash dan gunakanIFS=: read -ad '' array
untuk mengimpor.read
. backslash-newline untukread
adalah cara untuk melanjutkan garis logis ke garis fisik lain. Sunting: ah saya melihat Anda menyebutkan masalah dengan baris baru sudah.Anda bisa menggunakan
base64
:sumber
Cara lain untuk melakukannya adalah memastikan Anda menangani semua
'
hardquote seperti ini:Atau dengan
export
:Opsi pertama dan kedua bekerja di setiap shell POSIX, dengan asumsi bahwa nilai variabel tidak mengandung string:
Opsi ketiga harus bekerja untuk setiap shell POSIX tetapi dapat mencoba untuk mendefinisikan variabel lain seperti
_
atauPWD
. Kenyataannya adalah, bahwa satu-satunya variabel yang mungkin berusaha untuk didefinisikan ditetapkan dan dikelola oleh shell itu sendiri - dan bahkan jika Anda melakukan imporexport
nilai untuk salah satu dari mereka - seperti$PWD
misalnya - shell hanya akan mengatur ulang ke tetap nilai yang benar - coba lakukanPWD=any_value
dan lihat sendiri.Dan karena - setidaknya dengan GNU's
bash
- debug output secara otomatis dikutip untuk re-input ke shell, ini berfungsi terlepas dari jumlah'
kutipan keras di"$VAR"
:$VAR
nantinya dapat diatur ke nilai yang disimpan dalam skrip apa pun di mana jalur berikut ini valid dengan:sumber
$$
adalah PID shell yang berjalan, apakah Anda salah mengutip dan berarti\$
atau sesuatu? Pendekatan dasar menggunakan dokumen di sini bisa dilakukan untuk bekerja, tetapi itu rumit, bukan materi satu-liner: apa pun yang Anda pilih sebagai penanda akhir, Anda harus memilih sesuatu yang tidak muncul dalam string.$VAR
berisi%
. Perintah ketiga tidak selalu bekerja dengan nilai-nilai yang mengandung banyak baris (bahkan setelah menambahkan tanda kutip ganda yang jelas hilang).env
. Saya masih ingin tahu apa yang Anda maksud tentang beberapa baris -sed
menghapus setiap baris sampai bertemuVAR=
sampai akhir - jadi semua baris$VAR
diteruskan. Bisakah Anda memberikan contoh yang merusaknya?VAR
) tidak berubahPWD
atau_
atau yang lain yang didefinisikan oleh beberapa shell. Metode kedua membutuhkan bash; format output dari-v
tidak distandarisasi (tidak ada tanda hubung, ksh93, mksh dan zsh berfungsi).Hampir sama tetapi sedikit berbeda:
Dari skrip Anda:
Kali ini diuji.
sumber
'
,*
, dllecho "$LVALUE=\"$RVALUE\""
seharusnya menjaga baris baru juga dan hasil dalam cfg_file harus seperti: MY_VAR1 = "Line1 \ nLine 2" Jadi ketika eval MY_VAR1 itu akan berisi baris baru juga. Tentu saja Anda mungkin memiliki masalah jika nilai yang disimpan mengandung"
char sendiri . Tapi itu bisa diurus juga.