Apakah ada cara untuk membuat perl -i tidak clinkber symlink?

12

Seorang teman saya menunjukkan bahwa jika Anda melakukannya:

perl -pi.bak -e 's/foo/bar/' somefile

ketika "somefile" sebenarnya adalah symlink, perl melakukan apa yang akan dilakukan dokumen:

Ini dilakukan dengan mengganti nama file input, membuka file output dengan nama asli, dan memilih file output sebagai default untuk pernyataan print (). Ekstensi, jika disediakan, digunakan untuk mengubah nama file lama untuk membuat salinan cadangan [...]

Yang menghasilkan symlink baru "somefile.bak" yang menunjuk ke file nyata yang tidak berubah, dan yang baru, mengubah file biasa "somefile" dengan perubahan.

Dalam banyak kasus, mengikuti symlink akan menjadi perilaku yang diinginkan (bahkan jika itu meninggalkan lokasi yang benar dari file .bak ambigu). Apakah ada cara sederhana untuk melakukan ini selain menguji untuk symlink dalam bungkus dan menangani kasing dengan tepat?

( sedmelakukan hal yang sama, untuk apa nilainya.)

mattdm
sumber
1
Sebut vim atau emacs (saya pikir keduanya mengikuti symlinks)? Serius, saya khawatir jawabannya adalah menerapkan kembali -p -idalam skrip Anda.
Gilles 'SANGAT berhenti menjadi jahat'
Sed sebenarnya tidak melakukan hal yang sama, setidaknya tidak sejak versi 4.2.1, dirilis pada Juni 2009. Anda harus menyertakan opsi --follow-symlinks untuk mengedit untuk mengedit file yang ditautkan-ke daripada menabrak symlink ; Saya menganggap ini dilakukan untuk menghindari kerusakan skrip yang ada yang mungkin tergantung pada perilaku lama.
Garrett

Jawaban:

6

Saya bertanya-tanya apakah spongeutilitas serba guna kecil ("menyerap input standar dan menulis ke file") dari moreutils akan membantu dalam kasus ini dan apakah ia akan mengikuti symlink.

Penulis menggambarkan sponge seperti ini:

Ini mengatasi masalah pengeditan file di tempat dengan alat Unix, yaitu bahwa jika Anda hanya mengarahkan output ke file yang Anda coba edit maka pengalihan akan berlaku (menghancurkan isi file) sebelum perintah pertama dalam pipa. mendapat putaran untuk membaca dari file. Beralih suka sed -idan perl -ibekerja di sekitar ini, tetapi tidak setiap perintah yang mungkin ingin Anda gunakan dalam pipa memiliki opsi seperti itu, dan Anda tidak dapat menggunakan pendekatan itu dengan pipa banyak perintah.

Saya biasanya menggunakan spons sedikit seperti ini:

sed '...' file | grep '...' | sponge file
imz - Ivan Zakharyaschev
sumber
Hei keren, itu berhasil. Saya menduga komentar Gilles di atas adalah jawaban "nyata", tetapi karena dia tidak menjadikannya sebagai jawaban, dan karena saya mempelajari utilitas baru, saya akan mengambil yang ini. :)
mattdm
Tetapi apakah Anda sudah menguji spongepenggunaannya dalam praktik? Bagi saya: belum. Bisakah Anda meninggalkan komentar yang menyatakan apakah itu berlaku dalam tes seperti yang diinginkan di sini? Oh, saya melihat komentar. Terima kasih atas konfirmasinya!
imz - Ivan Zakharyaschev
- ya, saya mencobanya sebelum menjawab. Ketika filedalam contoh Anda di atas adalah symlink, tautan dibiarkan sendiri dan file asli berubah.
mattdm
@mattdm: Ya, terima kasih untuk konfirmasi! (Saya perhatikan kata-kata Anda "yang bekerja" sedikit lebih lambat daripada saya menulis komentar saya.)
imz - Ivan Zakharyaschev
3

Jika Anda mengetahui fakta bahwa itu somefileadalah symlink, Anda dapat secara eksplisit menyediakan path lengkap ke file dengan yang berikut ini:

perl -pi.bak -e 's/foo/bar/' $(readlink somefile)

Dengan begitu, symlink asli tetap utuh karena penggantian sekarang dilakukan langsung dengan file asli.

IDDQD
sumber
Tetapi output dari readlinkmungkin masih symlink lain. Terbaik akan menggunakan -fatau -edi mana tersedia atau zsh's $file:Patau (:P)gumpal kualifikasi seperti yang ditunjukkan oleh @ikegami untuk mendapatkan jalan symlink bebas.
Stéphane Chazelas
3

Jika Anda berurusan dengan satu file itu, perintahnya akan terlihat sebagai berikut:

perl -i -pe'...' -- "$qfn"

Solusinya adalah sebagai berikut:

perl -i -pe'...' -- "$( readlink -e -- "$qfn" )"

Ini menangani symlink dan non-symlink.


Jika Anda berurusan dengan sejumlah besar file secara sewenang-wenang, perintahnya akan terlihat sebagai berikut:

... | xargs -r perl -i -pe'...' --

Solusinya adalah sebagai berikut:

... | xargs -r readlink -ze -- | xargs -r0 perl -i -pe'...' --

Ini menangani symlink dan non-symlink.


Peringatan readlink bukanlah perintah standar, jadi itu keberadaan, argumennya, dan fungsinya bervariasi menurut sistem, jadi pastikan untuk memeriksa dokumentasi Anda. Sebagai contoh, beberapa implementasi memiliki -f(yang juga bisa Anda gunakan di sini), tetapi tidak -e, beberapa tidak. busyboxIni readlink -fberperilaku seperti GNU readlink -e. Dan beberapa sistem memiliki realpathperintah alih-alih readlinkyang umumnya berperilaku seperti GNU readlink -f.

Berhati-hatilah dengan GNU readlink -e, symlink yang tidak dapat diselesaikan ke file yang ada dihapus secara diam-diam.

ikegami
sumber
@ Stéphane Chazelas, $var:Ptidak bekerja untuk saya. ( X='tmp' zsh -c 'perl -E"say for @ARGV" $X:P'keluaran tmp:P)
ikegami
Anda memerlukan zsh 5.3 (2016) atau lebih untuk $var:P. Dengan versi yang lebih lama, Anda selalu dapat menggunakannya $var:A. Lihat manual untuk perbedaan dan zsh.org/mla/users/2016/msg00593.html untuk alasan untuk memperkenalkan :P( realpath()antarmuka nyata ).
Stéphane Chazelas
( :Aditambahkan pada 4.3.10 (2009), GNU readlink -zpada 2012, saya tidak mengetahui adanya readlinkimplementasi lain yang mendukung -z). Perhatikan bahwa dengan GNU xargs, Anda biasanya menginginkan -ropsi kecuali Anda dapat menjamin input tidak akan kosong. Di sini, ini bukan masalah besar (kecuali perlkode berisi BEGIN/ ENDpernyataan), Anda hanya akan mendapat -i used with no filenames on the command line, reading from STDIN.peringatan jika itu terjadi.
Stéphane Chazelas
Ups, saya salah membaca -rdokumentasi. Saya pikir saya melakukan kebalikan dari apa yang dilakukannya. Siap
ikegami