Saya selalu benar-benar ragu-ragu untuk dipusingkan $IFS
karena hal itu menghancurkan global.
Tetapi seringkali itu membuat memuat string menjadi bash array bagus dan ringkas, dan untuk bash scripting, keringkasan sulit didapat.
Jadi saya pikir mungkin lebih baik daripada tidak sama sekali jika saya mencoba "menyimpan" konten awal dari $IFS
variabel lain dan mengembalikannya segera setelah saya selesai menggunakan $IFS
sesuatu.
Apakah ini praktis? Atau pada dasarnya itu tidak ada gunanya dan saya harus langsung IFS
kembali ke apa pun yang diperlukan untuk penggunaan selanjutnya?
bash
shell-script
Steven Lu
sumber
sumber
$' \t\n'
jika Anda menggunakan bash.unset $IFS
tidak selalu mengembalikannya ke apa yang Anda harapkan sebagai default.Jawaban:
Anda dapat menyimpan dan menetapkan ke IFS sesuai kebutuhan. Tidak ada yang salah dengan melakukannya. Tidak jarang menyimpan nilainya untuk restorasi setelah modifikasi sementara yang cepat, seperti contoh penetapan array Anda.
Seperti @llua menyebutkan dalam komentarnya untuk pertanyaan Anda, hanya dengan tidak mengaktifkan IFS akan mengembalikan perilaku default, setara dengan menetapkan spasi-tab-baris baru.
Ada baiknya mempertimbangkan bagaimana hal itu bisa lebih bermasalah untuk tidak secara eksplisit mengatur / menghapus IFS daripada melakukannya.
Dari edisi POSIX 2013, 2.5.3 Variabel Shell :
Shell yang mematuhi POSIX, dipanggil mungkin atau mungkin tidak mewarisi IFS dari lingkungannya. Dari berikut ini:
"$*"
), tetapi yang dapat berjalan di bawah shell yang menginisialisasi IFS dari lingkungan, harus secara eksplisit mengatur / membatalkan pengaturan IFS untuk mempertahankan diri terhadap intrusi lingkungan.NB Penting untuk dipahami bahwa untuk diskusi ini kata "dipanggil" memiliki arti tertentu. Sebuah shell dipanggil hanya ketika secara eksplisit dipanggil menggunakan namanya (termasuk
#!/path/to/shell
shebang). Subshell - seperti yang mungkin dibuat oleh$(...)
ataucmd1 || cmd2 &
- bukan shell yang dipanggil, dan IFS-nya (bersama dengan sebagian besar lingkungan eksekusinya) identik dengan induknya. Shell yang dipanggil menetapkan nilai$
untuk pidnya, sementara subkulit mewarisinya.Ini bukan semata-mata pembebasan dendam; ada perbedaan nyata dalam bidang ini. Berikut ini adalah skrip singkat yang menguji skenario menggunakan beberapa shell yang berbeda. Ini mengekspor IFS yang dimodifikasi (diatur ke
:
) ke shell yang dipanggil yang kemudian mencetak IFS default.IFS umumnya tidak ditandai untuk ekspor, tetapi, jika ya, perhatikan bagaimana bash, ksh93, dan mksh mengabaikan lingkungan mereka
IFS=:
, sementara dash dan busybox menghormatinya.Beberapa info versi:
Meskipun bash, ksh93, dan mksh tidak menginisialisasi IFS dari lingkungan, mereka mengekspor kembali IFS yang dimodifikasi.
Jika karena alasan apa pun Anda perlu melewati IFS dengan mudah melalui lingkungan, Anda tidak dapat melakukannya dengan menggunakan IFS itu sendiri; Anda perlu menetapkan nilai ke variabel yang berbeda dan menandai variabel itu untuk ekspor. Anak-anak kemudian perlu secara eksplisit menetapkan nilai itu ke IFS mereka.
sumber
IFS
nilai dalam sebagian besar situasi di mana itu akan digunakan, dan seringkali tidak produktif untuk mencoba "mempertahankan" nilai aslinya.read
dikutip , penggantian perintah yang tidak dikutip , ekspansi aritmatika yang tidak dikutip , s, atau referensi kutipan ganda$*
. Daftar itu berada di luar kepala saya, jadi mungkin tidak komprehensif (terutama ketika mempertimbangkan POSIX-extensions of modern shells).Secara umum, ini adalah praktik yang baik untuk mengembalikan kondisi ke default.
Namun, dalam hal ini, tidak terlalu banyak.
Mengapa?:
$' \t\n'
.unset IFS
membuatnya bertindak seolah-olah disetel ke default .Juga, menyimpan nilai IFS memiliki masalah.
Jika IFS asli tidak disetel, kode
IFS="$OldIFS"
akan mengatur IFS ke""
, bukan membatalkannya.Untuk benar-benar menjaga nilai IFS (bahkan jika tidak disetel), gunakan ini:
sumber
bash
,unset IFS
gagal mengeset IFS jika telah dinyatakan lokal dalam konteks induk (konteks fungsi) dan tidak dalam konteks saat ini.Anda benar untuk ragu-ragu untuk mengalahkan global. Jangan takut, adalah mungkin untuk menulis kode kerja bersih tanpa pernah memodifikasi global yang sebenarnya
IFS
, atau melakukan save / restore dance yang rumit dan rentan kesalahan.Kamu bisa:
atur IFS untuk satu permintaan:
atau
mengatur IFS di dalam subkulit:
Contohnya
Untuk mendapatkan string yang dibatasi koma dari array:
Catatan: Ini
-
hanya untuk melindungi array kosong terhadapset -u
dengan memberikan nilai default saat tidak disetel (nilai tersebut menjadi string kosong dalam kasus ini) .The
IFS
modifikasi hanya berlaku di dalam subkulit melahirkan dengan$()
substitusi perintah . Ini karena subkulit memiliki salinan variabel shell yang memohon dan karenanya dapat membaca nilainya, tetapi setiap modifikasi yang dilakukan oleh subkulit hanya memengaruhi salinan subkulit dan bukan variabel induk.Anda mungkin juga berpikir: mengapa tidak melewatkan subshell dan lakukan saja ini:
Tidak ada permintaan perintah di sini, dan baris ini malah ditafsirkan sebagai dua penugasan variabel berikutnya yang independen, seolah-olah:
Akhirnya, mari kita jelaskan mengapa varian ini tidak akan berfungsi:
The
echo
Perintah memang akan disebut dengan nyaIFS
variabel set untuk,
, tetapiecho
tidak peduli atau penggunaanIFS
. Keajaiban memperluas"${array[*]}"
ke string dilakukan oleh (sub-) shell itu sendiri sebelumecho
bahkan dipanggil.Untuk membaca seluruh file (yang tidak mengandung
NULL
byte) ke dalam satu variabel bernamaVAR
:Catatan:
IFS=
sama denganIFS=""
danIFS=''
, yang semuanya mengatur IFS ke string kosong, yang sangat berbeda dariunset IFS
: jikaIFS
tidak disetel, perilaku semua fungsi bash yang digunakan secara internalIFS
persis sama seperti jikaIFS
memiliki nilai default$' \t\n'
.Pengaturan
IFS
ke string kosong memastikan spasi putih memimpin dan tertinggal dipertahankan.Perintah
-d ''
atau-d ""
read read hanya menghentikan permintaannya saat ini padaNULL
byte, bukan pada baris baru yang biasa.Untuk membagi
$PATH
bersama nya:
pembatas:Contoh ini murni ilustratif. Dalam kasus umum di mana Anda membelah sepanjang pembatas, dimungkinkan untuk masing-masing bidang berisi (versi yang lolos dari) pembatas itu. Bayangkan mencoba membaca dalam satu baris
.csv
file yang kolomnya sendiri mungkin berisi koma (lolos atau dikutip dengan cara tertentu). Cuplikan di atas tidak akan berfungsi sebagaimana dimaksud untuk kasus tersebut.Yang mengatakan, Anda tidak mungkin menemukan
:
-containing-path di dalamnya$PATH
. Sementara UNIX / Linux pathnames diizinkan mengandung:
, tampaknya bash tidak akan mampu menangani jalur seperti itu jika Anda mencoba menambahkannya ke Anda$PATH
dan menyimpan file yang dapat dieksekusi di dalamnya, karena tidak ada kode untuk mengurai kolon yang lolos / dikutip titik dua : kode sumber bash 4.4 .Akhirnya, perhatikan bahwa snippet menambahkan baris baru ke elemen terakhir dari array yang dihasilkan (sebagaimana dipanggil oleh @ StéphaneChazelas dalam komentar yang sekarang dihapus), dan bahwa jika inputnya adalah string kosong, output akan menjadi elemen tunggal. array, di mana elemen akan terdiri dari baris baru (
$'\n'
).Motivasi
old_IFS="${IFS}"; command; IFS="${old_IFS}"
Pendekatan dasar yang menyentuh globalIFS
akan bekerja seperti yang diharapkan untuk skrip yang paling sederhana. Namun, segera setelah Anda menambahkan kerumitan, hal itu dapat dengan mudah pecah dan menyebabkan masalah halus:command
adalah fungsi bash yang juga memodifikasi globalIFS
(baik secara langsung atau, disembunyikan dari tampilan, di dalam fungsi lain yang ia panggil), dan saat melakukannya secara keliru menggunakanold_IFS
variabel global yang sama untuk melakukan save / restore, Anda mendapatkan bug.IFS
tidak disetel, save-and-restore yang naif tidak akan berfungsi, dan bahkan akan mengakibatkan kegagalan langsung jika opsi shell yang umum (salah) digunakanset -u
(aliasset -o nounset
) digunakan berlaku.help trap
). Jika kode itu juga memodifikasi globalIFS
atau menganggapnya memiliki nilai tertentu, Anda bisa mendapatkan bug yang halus.Anda dapat menyusun urutan simpan / kembalikan yang lebih kuat (seperti yang diusulkan dalam jawaban lain ini untuk menghindari beberapa atau semua masalah ini. Namun, Anda harus mengulangi potongan kode boilerplate yang berisik itu di mana pun Anda sementara membutuhkan kustom
IFS
. Ini mengurangi keterbacaan dan pemeliharaan kode.Pertimbangan tambahan untuk skrip mirip perpustakaan
IFS
secara khusus menjadi perhatian bagi penulis perpustakaan fungsi shell yang perlu memastikan kode mereka bekerja dengan baik terlepas dari keadaan global (IFS
, opsi shell, ...) yang diberlakukan oleh penjajah mereka, dan juga tanpa mengganggu keadaan itu sama sekali (para penjajah mungkin mengandalkan di atasnya untuk selalu tetap statis).Saat menulis kode pustaka, Anda tidak dapat mengandalkan
IFS
memiliki nilai tertentu (bahkan bukan nilai default) atau bahkan disetel sama sekali. Sebagai gantinya, Anda perlu menetapkan secara eksplisitIFS
untuk cuplikan apa pun yang perilakunya tergantung padaIFS
.Jika
IFS
secara eksplisit diatur ke nilai yang diperlukan (bahkan jika itu kebetulan menjadi nilai default) di setiap baris kode di mana nilai penting menggunakan salah satu dari dua mekanisme yang dijelaskan dalam jawaban ini yang sesuai untuk melokalisasi efek, maka kode keduanya tidak tergantung pada negara global dan tidak akan menghalanginya sama sekali. Pendekatan ini memiliki manfaat tambahan membuatnya sangat eksplisit bagi seseorang yang membaca skrip yangIFS
penting untuk perintah / ekspansi yang satu ini dengan biaya tekstual minimum (dibandingkan dengan bahkan penyimpanan / pengembalian paling dasar).Lagipula kode apa yang terpengaruh
IFS
?Untungnya, tidak ada banyak skenario yang
IFS
penting (dengan asumsi Anda selalu mengutip ekspansi Anda ):"$*"
dan"${array[*]}"
ekspansiread
beberapa variabel penargetan bawaan (read VAR1 VAR2 VAR3
) atau variabel array (read -a ARRAY_VAR_NAME
)read
menargetkan variabel tunggal ketika datang ke terkemuka / trailing spasi atau non-spasi karakter yang muncul diIFS
.sumber
:
kapan:
pembatas?:
adalah karakter yang valid untuk digunakan dalam nama file pada kebanyakan sistem file UNIX / Linux, jadi sangat mungkin untuk memiliki direktori dengan nama yang mengandung:
. Mungkin beberapa shell memiliki ketentuan untuk melarikan diri:
di PATH dengan menggunakan sesuatu seperti\:
, dan kemudian Anda akan melihat kolom muncul yang bukan pembatas yang sebenarnya (Tampaknya bash tidak memungkinkan melarikan diri seperti itu. Fungsi tingkat rendah yang digunakan ketika beralih lebih dari$PATH
hanya mencari:
di a C string: git.savannah.gnu.org/cgit/bash.git/tree/general.c#n891 ).$PATH
contoh pemisahan menjadi:
lebih jelas.Mengapa beresiko salah ketik pengaturan IFS
$' \t\n'
ketika semua yang harus Anda lakukan adalahAtau, Anda dapat memanggil subkulit jika Anda tidak memerlukan variabel apa pun yang diatur / dimodifikasi dalam:
sumber
IFS
awalnya tidak disetel.