Pengguna memanggil skrip saya dengan jalur file yang akan dibuat atau ditimpa pada beberapa titik dalam skrip, seperti foo.sh file.txt
atau foo.sh dir/file.txt
.
Perilaku create-or-overwrite mirip dengan persyaratan untuk meletakkan file di sisi kanan >
operator redirect output, atau meneruskannya sebagai argumen untuk tee
(pada kenyataannya, meneruskannya sebagai argumen tee
adalah apa yang saya lakukan ).
Sebelum saya masuk ke nyali script, saya ingin melakukan pemeriksaan yang masuk akal apakah file tersebut dapat dibuat / ditimpa, tetapi tidak benar-benar membuatnya. Pemeriksaan ini tidak harus sempurna, dan ya saya menyadari bahwa situasinya dapat berubah antara cek dan titik di mana file tersebut sebenarnya ditulis - tetapi di sini saya OK dengan solusi jenis upaya terbaik sehingga saya dapat menyelamatkan lebih awal dalam hal path file tidak valid.
Contoh alasan file tidak dapat dibuat:
- file berisi komponen direktori, seperti
dir/file.txt
tetapi direktoridir
tidak ada - pengguna tidak memiliki izin menulis di direktori yang ditentukan (atau CWD jika tidak ada direktori yang ditentukan
Ya, saya menyadari bahwa memeriksa izin "di muka" bukan The UNIX Way ™ , melainkan saya harus mencoba operasi dan meminta pengampunan nanti. Namun dalam skrip khusus saya, ini mengarah pada pengalaman pengguna yang buruk dan saya tidak dapat mengubah komponen yang bertanggung jawab.
sumber
/foo/bar/file.txt
. Pada dasarnya saya melewati jalan untuktee
menyukaitee $OUT_FILE
tempatOUT_FILE
dilewatkan pada baris perintah. Itu harus "hanya bekerja" dengan jalur absolut dan relatif, bukan?tee -- "$OUT_FILE"
. Bagaimana jika file tersebut sudah ada atau ada tetapi bukan file biasa (direktori, symlink, fifo)?tee "${OUT_FILE}.tmp"
. Jika file sudah ada,tee
timpa, yang merupakan perilaku yang diinginkan dalam kasus ini. Jika itu direktori,tee
akan gagal (saya pikir). symlink Saya tidak yakin 100%?Jawaban:
Tes yang jelas adalah:
Tapi itu benar-benar membuat file jika belum ada di sana. Kita bisa membersihkan diri kita sendiri:
Tapi ini akan menghapus file yang sudah ada, yang mungkin tidak Anda inginkan.
Namun, kami memiliki cara untuk mengatasi hal ini:
Anda tidak dapat memiliki direktori dengan nama yang sama dengan objek lain di direktori itu. Saya tidak dapat memikirkan situasi di mana Anda dapat membuat direktori tetapi tidak membuat file. Setelah tes ini, skrip Anda akan bebas untuk membuat yang konvensional
/path/to/file
dan melakukan apa pun yang diinginkan.sumber
if mkdir /var/run/lockdir
sebagai tes penguncian. Ini adalah atomik, sementaraif ! [[ -f /var/run/lockfile ]]; then touch /var/run/lockfile
memiliki sedikit waktu di antara pengujian dan ditouch
mana contoh skrip yang lain dapat dimulai.Dari apa yang saya kumpulkan, Anda ingin memeriksa itu ketika menggunakan
(perhatikan
--
atau tidak akan berfungsi untuk nama file yang dimulai dengan -),tee
akan berhasil membuka file untuk ditulis.Yaitu:
Kami akan mengabaikan untuk sekarang filesystems seperti vfat, ntfs atau hfsplus yang memiliki batasan pada apa nilai byte yang mungkin berisi nama file, kuota disk, batas proses, selinux, apparmor atau mekanisme keamanan lainnya dengan cara, filesystem lengkap, tidak ada inode tersisa, perangkat file yang tidak dapat dibuka seperti itu karena suatu alasan atau lainnya, file yang dapat dieksekusi saat ini dipetakan dalam beberapa ruang alamat proses yang semuanya juga dapat mempengaruhi kemampuan untuk membuka atau membuat file.
Dengan
zsh
:Dalam
bash
atau shell seperti Bourne, ganti sajadengan:
Fitur-
zsh
spesifik di sini adalah$ERRNO
(yang memperlihatkan kode kesalahan dari panggilan sistem terakhir) dan$errnos[]
array asosiatif untuk menerjemahkan ke nama makro C standar yang sesuai. Dan$var:h
(dari csh) dan$var:P
(perlu zsh 5.3 atau di atas).bash belum memiliki fitur yang setara.
$file:h
bisa diganti dengandir=$(dirname -- "$file"; echo .); dir=${dir%??}
, atau dengan GNUdirname
:IFS= read -rd '' dir < <(dirname -z -- "$file")
.Sebab
$errnos[ERRNO] == ENOENT
, pendekatan bisa dijalankanls -Ld
pada file dan memeriksa apakah pesan kesalahan sesuai dengan kesalahan ENOENT. Melakukan hal itu dengan andal dan mudah dibawa memang rumit.Satu pendekatan dapat berupa:
(dengan asumsi bahwa pesan kesalahan berakhir dengan
syserror()
terjemahanENOENT
dan bahwa terjemahan itu tidak menyertakan a:
) dan kemudian, alih-alih[ -e "$file" ]
, lakukan:Dan periksa kesalahan ENOENT dengan
Bagian
$file:P
adalah yang paling sulit untuk dicapaibash
, terutama pada FreeBSD.FreeBSD memang memiliki
realpath
perintah danreadlink
perintah yang menerima-f
opsi, tetapi mereka tidak dapat digunakan dalam kasus di mana file tersebut adalah symlink yang tidak menyelesaikan. Itu sama denganperl
'sCwd::realpath()
.python
'sos.path.realpath()
tidak muncul untuk bekerja sama denganzsh
$file:P
, sehingga dengan asumsi bahwa setidaknya satu versi daripython
terinstal dan bahwa adapython
perintah yang mengacu pada salah satu dari mereka (yang tidak diberikan pada FreeBSD), Anda bisa melakukan:Tapi kemudian, Anda bisa melakukan semuanya
python
.Atau Anda bisa memutuskan untuk tidak menangani semua kasus sudut itu.
sumber
tee
begitu persyaratannya adalah bahwa file tersebut dapat dibuat jika tidak ada, atau dapat dipotong ke nol dan ditimpa jika ya (atau yang laintee
menangani itu).perl
,awk
,sed
ataupython
(atau bahkansh
,bash
...). Shell scripting adalah tentang menggunakan perintah terbaik untuk tugas tersebut. Sini bahkanperl
tidak memiliki setara denganzsh
's$var:P
(yangCwd::realpath
berperilaku seperti BSDrealpath
ataureadlink -f
saat Anda ingin untuk berperilaku seperti GNUreadlink -f
ataupython
' sos.path.realpath()
)Salah satu opsi yang mungkin ingin Anda pertimbangkan adalah membuat file sejak awal tetapi hanya mengisi nanti di skrip Anda. Anda dapat menggunakan
exec
perintah untuk membuka file dalam deskriptor file (seperti 3, 4, dll.) Dan kemudian menggunakan redirection ke deskriptor file (>&3
, dll.) Untuk menulis konten ke file itu.Sesuatu seperti:
Anda juga dapat menggunakan
trap
untuk membersihkan file pada kesalahan, itu adalah praktik umum.Dengan cara ini, Anda mendapatkan cek nyata untuk izin bahwa Anda akan dapat membuat file, sementara pada saat yang sama dapat melakukannya cukup awal sehingga jika gagal Anda belum menghabiskan waktu menunggu cek panjang.
DIPERBARUI: Untuk menghindari clobbering file jika cek gagal, gunakan
fd<>file
pengalihan bash yang tidak memotong file segera. (Kami tidak peduli tentang membaca dari file, ini hanya solusi sehingga kami tidak memotongnya. Menambahkan dengan>>
mungkin hanya akan bekerja juga, tapi saya cenderung menemukan ini sedikit lebih elegan, menjaga bendera O_APPEND keluar gambar.)Ketika kita siap untuk mengganti konten, kita perlu memotong file terlebih dahulu (jika tidak, kita menulis byte lebih sedikit daripada yang ada di file sebelumnya, trailing byte akan tetap di sana.) Kita dapat menggunakan truncate (1) perintah dari coreutils untuk tujuan itu, dan kita dapat memberikannya deskriptor file terbuka yang kita miliki (menggunakan file
/dev/fd/3
pseudo) sehingga tidak perlu membuka kembali file. (Sekali lagi, secara teknis sesuatu yang lebih sederhana seperti: >dir/file.txt
mungkin akan berfungsi, tetapi tidak harus membuka kembali file adalah solusi yang lebih elegan.)sumber
echo -n >>file
atautrue >> file
. Tentu saja, ext4 memiliki file append-only, tetapi Anda bisa hidup dengan false positive itu.my-file.txt.backup
) Sebelum membuat yang baru. Solusi lain adalah dengan menulis file baru ke file sementara di folder yang sama, dan kemudian menyalinnya di file lama setelah sisa skrip berhasil --- jika operasi terakhir gagal, pengguna dapat memperbaiki masalah secara manual tanpa kehilangan kemajuannya.Saya pikir solusi DopeGhoti lebih baik tetapi ini juga harus bekerja:
Yang pertama jika konstruk memeriksa apakah argumennya adalah path lengkap (dimulai dengan
/
) dan menetapkandir
variabel ke path direktori hingga yang terakhir/
. Kalau tidak, jika argumen tidak dimulai dengan/
tetapi mengandung/
(menentukan sub direktori) itu akan diaturdir
ke direktori kerja sekarang + jalur direktori sub. Kalau tidak, itu mengasumsikan direktori kerja saat ini. Kemudian memeriksa apakah direktori itu dapat ditulisi.sumber
Bagaimana dengan menggunakan
test
perintah normal seperti diuraikan di bawah ini?sumber
man test
, dan itu[ ]
setara dengan tes (bukan pengetahuan umum lagi). Berikut ini adalah kalimat yang akan saya gunakan dalam jawaban saya yang lebih rendah, untuk membahas kasus yang tepat, untuk anak cucu:if [ -w $1] && [ ! -d $1 ] ; then echo "do stuff"; fi
Anda menyebutkan pengalaman pengguna mengarahkan pertanyaan Anda. Saya akan menjawab dari sudut UX, karena Anda sudah mendapatkan jawaban yang baik di sisi teknis.
Daripada melakukan pemeriksaan di muka, bagaimana dengan menulis hasil ke file sementara kemudian di bagian paling akhir, menempatkan hasilnya ke file yang diinginkan pengguna? Suka:
Yang baik dengan pendekatan ini: ini menghasilkan operasi yang diinginkan dalam skenario jalur bahagia normal, langkah-langkah sisi masalah atomisitas uji-dan-set, mempertahankan izin file target sambil membuat jika perlu, dan sudah mati sederhana untuk diimplementasikan.
Catatan: jika kami menggunakan
mv
, kami akan menjaga izin file sementara - kami tidak menginginkan itu, saya pikir: kami ingin menjaga izin seperti yang ditetapkan pada file target.Sekarang, yang buruk: ini membutuhkan ruang dua kali lipat (
cat .. >
membangun), memaksa pengguna untuk melakukan pekerjaan manual jika file target tidak dapat ditulisi pada saat diperlukan, dan meninggalkan file sementara tergeletak di sekitar (yang mungkin memiliki keamanan atau masalah pemeliharaan).sumber
TL; DR:
Tidak seperti
<> "${userfile}"
atautouch "${userfile}"
, ini tidak akan membuat modifikasi palsu pada stempel waktu file dan juga akan bekerja dengan file tulis saja.Dari OP:
Dan dari komentar Anda hingga jawaban saya dari perspektif UX :
Satu-satunya tes yang dapat diandalkan adalah ke
open(2)
file, karena hanya itu yang menyelesaikan setiap pertanyaan tentang kemampuan menulis: jalur, kepemilikan, sistem file, jaringan, konteks keamanan, dll. Tes lain akan membahas beberapa bagian kemampuan menulis, tetapi tidak yang lain. Jika Anda ingin bagian dari tes, pada akhirnya Anda harus memilih apa yang penting bagi Anda.Tapi ini pemikiran lain. Dari apa yang saya mengerti:
Anda ingin melakukan pra-pemeriksaan ini karena # 1, dan Anda tidak ingin mengutak-atik file yang ada karena # 2. Jadi mengapa Anda tidak meminta shell untuk membuka file append, tetapi tidak benar-benar menambahkan apa pun?
Di bawah tenda, ini menyelesaikan pertanyaan kemampuan menulis persis seperti yang diinginkan:
tetapi tanpa memodifikasi isi atau metadata file. Sekarang, ya, pendekatan ini:
lsattr
dan bereaksi sesuai.rm
.Sementara saya berpendapat (dalam jawaban saya yang lain) bahwa pendekatan yang paling ramah pengguna adalah membuat file sementara yang harus dipindahkan oleh pengguna, saya pikir ini adalah pendekatan yang paling tidak ramah pengguna untuk sepenuhnya memeriksa input mereka.
sumber