Mengapa memeriksa keberadaan file sebelum mengambilnya?


13

Saat mencoba sumber file, tidakkah Anda ingin kesalahan mengatakan file tidak ada sehingga Anda tahu apa yang harus diperbaiki?

Misalnya, nvm merekomendasikan untuk menambahkan ini ke profil / rc Anda:

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm

Dengan di atas, jika nvm.shtidak ada, Anda akan mendapatkan "kesalahan diam". Tetapi jika Anda mencoba . "$NVM_DIR/nvm.sh", hasilnya akan menjadi FILE_PATH: No such file or directory.


3
Anda seharusnya tidak. Ini bersemangat. Coba sumber lalu tangani kesalahan, jika ada
Mikel

2
@Mikel benar! lalu mengapa saya melihatnya di mana-mana?
JBallin

3
@FaheemMitha, well, jika apa yang Anda lakukan sama sekali peka terhadap keamanan (yaitu program Anda bekerja atas nama orang lain), maka Anda benar-benar perlu peduli dengan kondisi lomba ( TOCTOU ). Itu mungkin tidak terjadi di sini, karena Anda memiliki masalah yang lebih besar jika seseorang dapat memodifikasi file HOME.
ilkkachu

8
@FaheemMitha Tidak pernah lebih baik untuk memeriksa keberadaannya terlebih dahulu. Situasi dapat berubah antara pengujian dan penggunaan, menghasilkan positif palsu dan negatif palsu: dan Anda masih harus menangani kegagalan penggunaan. Dan seperti yang telah Anda sebutkan kinerja, pengujian sebelum digunakan secara endemik dua kali lebih lambat daripada tidak melakukannya dan membiarkan sistem melakukannya, yang tetap harus dilakukan. Tidak mungkin tidak.
user207421

1
@SimonRichter, dalam hal ini nvm tidak akan berfungsi. Pengguna harus mengetahui bahwa file tersebut hilang dengan sendirinya.
JBallin

Jawaban:


25

Dalam shell POSIX, .adalah builtin khusus, sehingga kegagalannya menyebabkan shell untuk keluar (dalam beberapa shell seperti bash, itu hanya dilakukan ketika dalam mode POSIX).

Apa yang memenuhi syarat sebagai kesalahan tergantung pada shell. Tidak semua dari mereka keluar karena kesalahan sintaks saat mem-parsing file, tetapi sebagian besar akan keluar ketika file yang bersumber tidak dapat ditemukan atau dibuka. Saya tidak tahu ada yang akan keluar jika perintah terakhir dalam file yang bersumber dikembalikan dengan status keluar yang tidak nol (kecuali jika errexitopsi ini aktif tentu saja).

Di sini melakukan:

[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

Merupakan kasus di mana Anda ingin sumber file jika ada, dan jangan jika tidak (atau kosong di sini dengan -s).

Artinya, itu tidak boleh dianggap sebagai kesalahan (kesalahan fatal dalam kulit POSIX) jika file tidak ada, file itu dianggap sebagai file opsional.

Itu masih akan menjadi kesalahan (fatal) jika file tidak dapat dibaca atau direktori atau (dalam beberapa shell) jika ada kesalahan sintaksis saat menguraikannya yang akan menjadi kondisi kesalahan nyata yang harus dilaporkan.

Beberapa akan berpendapat bahwa ada kondisi balapan. Tapi satu-satunya artinya adalah bahwa shell akan keluar dengan kesalahan jika file dihapus di antara [dan ., tapi saya berpendapat itu valid untuk menganggapnya kesalahan bahwa file jalur tetap ini tiba-tiba akan hilang saat skrip berlari.

Di samping itu,

command . "$NVM_DIR/nvm.sh" 2> /dev/null

di mana command¹ menghapus atribut khusus ke .perintah (sehingga tidak keluar dari shell on error) tidak akan berfungsi sebagai:

  • itu akan menyembunyikan .kesalahan tetapi juga kesalahan dari perintah yang dijalankan di file bersumber
  • itu juga akan menyembunyikan kondisi kesalahan nyata seperti file yang memiliki izin salah.

Sintaksis umum lainnya (lihat misalnya grep -r /etc/default /etc/init*pada sistem Debian untuk skrip init yang belum dikonversi ke systemd(di mana EnvironmentFile=-/etc/default/servicedigunakan untuk menentukan file lingkungan opsional sebagai gantinya)) meliputi:

  • [ -e "$file" ] && . "$file"

    Periksa file itu di sana, masih sumber jika kosong. Kesalahan masih fatal jika tidak bisa dibuka (meskipun ada di sana, atau ada di sana). Anda dapat melihat lebih banyak varian seperti [ -f "$file" ](ada dan merupakan file biasa ), [ -r "$file" ](dapat dibaca), atau kombinasi keduanya.

  • [ ! -e "$file" ] || . "$file"

    Versi yang sedikit lebih baik. Membuatnya lebih jelas bahwa file tidak ada adalah kasus yang OK. Itu juga berarti $?akan mencerminkan status keluar dari perintah terakhir yang dijalankan $file(dalam kasus sebelumnya, jika Anda mendapatkannya 1, Anda tidak tahu apakah itu karena $filetidak ada atau jika perintah itu gagal).

  • command . "$file"

    Harapkan file ada di sana, tetapi jangan keluar jika tidak bisa diartikan.

  • [ ! -e "$file" ] || command . "$file"

    Kombinasi di atas: tidak masalah jika file tidak ada di sana, dan untuk cangkang POSIX, kegagalan untuk membuka (atau mengurai) file tersebut dilaporkan tetapi tidak fatal (yang mungkin lebih diinginkan ~/.profile).


¹ Catatan: zshNamun, Anda tidak dapat menggunakan commandseperti itu kecuali dalam shpersaingan; perhatikan bahwa di shell Korn, sourcesebenarnya adalah alias untuk command ., varian non-khusus.


Menarik! Saya tidak tahu tentang POSIX sh. Tetapi pertanyaannya adalah tentang .bash_profile. Saya kira lebih baik aman daripada menyesal, tetapi apakah bash pernah dalam mode POSIX saat .bash_profilebersumber?
Mikel

(Saya sadar Anda dapat menafsirkan pertanyaan ini sebagai penerapan yang lebih luas untuk semua shell POSIX berdasarkan membaca tautan sumber nvm dalam pertanyaan.)
Mikel

@Mikel, jawaban saya masih berlaku bashketika tidak dalam mode POSIX. Anda ingin [ -e /file ] && . /filejika Anda tidak menganggapnya sebagai kesalahan ketika file tidak ada. Sumber coba kemudian menangani kesalahan, jika ada yang tidak dapat dilakukan di sini.
Stéphane Chazelas

1
@Mikel, itu kontra-produktif. 1) yang tidak mencegah keluar setelah kesalahan dengan shell POSIX (atau bash dalam mode POSIX) 2), yang menduplikasi pesan kesalahan (milik Anda di stdout), .sudah akan melaporkan kesalahan (pada stderr). Dan jika maksudnya adalah untuk tidak menganggapnya sebagai kesalahan ketika file tidak ada, itu tidak benar (dan itu tidak mungkin, dari status keluar untuk mengetahui apakah .gagal karena file tidak ada atau tidak dapat dibaca, atau sudah tidak dapat diuraikan, atau perintah terakhir gagal) yang merupakan poin yang saya buat di sini dalam jawaban ini.
Stéphane Chazelas

1
Sehubungan dengan kondisi balapan - itu jauh lebih sedikit dari masalah IMHO untuk login gagal sekali (sementara sesuatu yang aneh terjadi pada sistem) daripada untuk login gagal secara konsisten (bahkan jika ada sesuatu yang tidak-benar-benar dalam konfigurasi pengguna -files). Jadi kondisi balapan dalam cek ini masih merupakan perbaikan karena tidak memiliki cek.
ruakh

5

nvmTanggapan Maintainer of :

mudah untuk menghapus nvm hanya dengan menghapus file; memaksa kerja tambahan (untuk melacak di mana garis adalah sumber nvm) tampaknya tidak terlalu berharga.

Interpretasi saya (dikombinasikan dengan penjelasan Stéphane yang sangat baik dan komentar Kusalananda):

Lebih sederhana dan lebih aman.

Itu membela terhadap kerang POSIX keluar pada startup karena file yang hilang (karena berbagai alasan). Mereka yang menggunakan shell non-POSIX (mis. Bash) dapat menghapus kondisional jika mereka mau.


1
Saya menentang interpretasi Anda bahwa itu "ditujukan untuk pemula". Ini defensif. Anda tidak ingin shell login pengguna berhenti tiba-tiba saat startup hanya karena file itu hilang (untuk alasan apa pun). Jika baris kode berada di salah satu file init shell di bawahnya /etc, maka itu memungkinkan sebagian pengguna memiliki file, dan beberapa tidak memiliki file tersebut. IMHO, nvmrespons pengelola hanya menyentuh satu aspek.
Kusalananda

1

Seperti JBallin dan Stéphane Chazelas , dalam shell POSIX, sumber file yang tidak ada akan menyebabkan kegagalan login.

Tetapi menambahkan tes untuk melihat apakah file itu ada dan kemudian mencoba untuk sumbernya dapat menyebabkan sesuatu yang disebut kondisi balapan. Jika ada perubahan nvm.shdi antara [ -s nvm.sh ]dan . nvm.sh, itu akan menyebabkan persis bug yang mereka coba cegah, meskipun jauh lebih jarang.

Secara umum, cara untuk mencegah kondisi balapan adalah dengan hanya mencoba hal yang ingin Anda lakukan, kemudian menangani kesalahan jika gagal, misalnya

. "$NVM_DIR/nvm.sh" || echo "Sourcing $NVM_DIR/nvm.sh failed" >&2

Ternyata ini tidak berfungsi di shell POSIX, karena, seperti di atas, .kegagalan akan menyebabkan shell keluar segera, sebelum penanganan kesalahan dapat berjalan.

Jawaban saya berpendapat bahwa kerang POSIX tidak relevan dengan pertanyaan ini, karena .bash_profileseharusnya tidak pernah berjalan dalam mode POSIX. Jadi kita bisa melakukan kode di atas saja.

Agar paling aman, kami dapat memastikan bahwa mode POSIX tidak berlaku, atau memastikan mode POSIX dinonaktifkan menggunakan teknik yang dijelaskan dalam /unix//a/383581/3169 .

Jawaban Stéphane memiliki beberapa saran yang berguna untuk bagaimana menangani semua kerang POSIX, yang saya pikir adalah maksud penulis nvm, tetapi agak berbeda dari apa yang ditanyakan di sini, itulah sebabnya kami memiliki beberapa pendekatan yang mungkin, tergantung pada tujuan Anda. .

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.