Apakah ada kerugian menggunakan rm $ (ls) untuk menghapus file?

13

Saya bertanya-tanya apakah menggunakan rm $(ls)untuk menghapus file (atau rm -r $(ls)menghapus direktori juga) aman? Karena di semua situs web, orang memberikan cara lain untuk melakukan ini meskipun perintah ini tampaknya jauh lebih mudah daripada perintah lainnya.

posixKing
sumber
3
Jawaban dasar, tidak. Tidak dapat menangani karakter khusus. Saya bisa menulis jawaban sedikit untuk menjelaskan ini lebih terinci
Sergiy Kolodyazhnyy
5
touch 'foo -r .. bar'; rm $(ls); kemana direktori orang tua saya pergi? Juga, apa jenis alternatif yang Anda lihat yang bahkan lebih rumit dari ini? rm *jauh lebih mudah untuk mengetik dan memikirkannya, dan lebih aman (tetapi tidak sepenuhnya aman; lihat jawaban Dennis).
Peter Cordes
1
Selain jawaban yang sangat baik, perlu diketahui bahwa lsdapat bervariasi antara implementasi, dan karena itu tidak standar. Tergantung pada apa yang Anda butuhkan, pertimbangkan alternatif seperti finddan stat. Anda harus menggunakan lshanya untuk konsumsi manusia, tidak pernah untuk digunakan oleh perintah lain atau dalam skrip.
Paddy Landau

Jawaban:

7

Apa yang ingin dilakukan?

  • ls daftar file dalam direktori saat ini
  • $(ls)menggantikan keluaran lstempat yang sebagai argumen untukrm
  • Pada dasarnya rm $(ls)dimaksudkan untuk menghapus semua file dalam direktori saat ini

Ada apa dengan gambar ini?

lstidak dapat menangani karakter khusus dengan benar dalam nama file. Pengguna Unix umumnya disarankan untuk menggunakan pendekatan yang berbeda . Saya juga menunjukkan hal itu dalam pertanyaan terkait tentang penghitungan nama file . Contohnya:

$ touch file$'\n'name                                                                                                    
$ ls                                                                                                                     
file?name
$ rm $(ls)
rm: cannot remove 'file': No such file or directory
rm: cannot remove 'name': No such file or directory
$ 

Juga, sebagaimana disebutkan dengan tepat dalam jawaban Denis, nama file dengan tanda hubung utama, dapat ditafsirkan sebagai argumen rmsetelah penggantian, yang mengalahkan tujuan menghapus nama file.

Pekerjaan apa

Anda ingin menghapus file di direktori saat ini. Jadi gunakan glob rm *:

$ ls                                                                                                                     
file?name
$ rm $(ls)
rm: cannot remove 'file': No such file or directory
rm: cannot remove 'name': No such file or directory
$ rm *
$ ls
$ 

Anda bisa menggunakan findperintah. Alat ini sering direkomendasikan untuk lebih dari sekedar direktori saat ini - ini dapat secara rekursif melintasi seluruh pohon direktori, dan beroperasi pada file melalui-exec . . .{} \;

$ touch "file name"                                
$ find . -maxdepth 1 -mindepth 1                                                                                         
./file name
$ find . -maxdepth 1 -mindepth 1 -exec rm {} \;                                                                          
$ ls
$ 

Python tidak memiliki masalah dengan karakter khusus dalam nama file, jadi kami juga dapat menggunakannya (perhatikan bahwa ini hanya untuk file, Anda harus menggunakan os.rmdir()dan os.path.isdir()jika Anda ingin beroperasi pada direktori):

python -c 'import os; [ os.remove(i) for i in os.listdir(".") if os.path.isfile(i) ]'

Bahkan, perintah di atas dapat diubah menjadi fungsi atau alias ~/.bashrcuntuk singkatnya. Sebagai contoh,

rm_stuff()
{
    # Clears all files in the current working directory
    python -c 'import os; [ os.remove(i) for i in os.listdir(".") if os.path.isfile(i) ]'

}

Versi Perl itu akan menjadi

perl -e 'use Cwd;my $d=cwd();opendir(DIR,$d); while ( my $f = readdir(DIR)){ unlink $f;}; closedir(DIR)'
Sergiy Kolodyazhnyy
sumber
1
"$(ls)"Contoh Anda hanya berfungsi jika hanya ada satu file di direktori. Anda mungkin juga cukup menggunakan tab-completion untuk memperluas nama file karena itu satu-satunya penyelesaian.
Peter Cordes
@PeterCordes memang, untuk alasan yang aneh itu hanya berfungsi dengan satu file. Itu satu lagi argumen yang menentang penggunaan ls:) Saya akan mengeditnya
Sergiy Kolodyazhnyy
Saya tidak akan menyebutnya alasan aneh: Anda mengutip $(ls)untuk menonaktifkan pemisahan kata, atau membiarkan pemisahan kata terjadi (dengan hasil yang merusak). Satu-satunya cara bersih untuk melewati daftar beberapa string tanpa memperlakukan data sebagai kode adalah dengan variabel array, atau dengan \0sebagai pemisah, tetapi shell itu sendiri tidak bisa melakukan itu. Masih IFS=$'\n'yang paling berbahaya, tetapi tidak bisa ditandingi find -print0 | xargs -0. Atau grep -l --null. Atau Anda menghindari seluruh masalah dengan hal-hal seperti find -exec rm {} +. (Catat +untuk memberikan beberapa argumen ke setiap permintaan rm; JAUH lebih efisien).
Peter Cordes
@PeterCordes Yup, sepenuhnya setuju di sana. Tetapi IFS=$'\n'akan gagal dalam kasus ini, juga, karena saya sudah baris baru dalam nama file, jadi kata-kata akan memperlakukannya sebagai dua nama file, bukan satu. Alasan anehnya, bagaimanapun, adalah fakta bahwa dengan default IFSyaitu spasi, tab, baris baru, asli rm "$(ls)"juga harus gagal, seharusnya memperlakukan seperti saya katakan nama file sebagai dua yang terpisah, tetapi tidak. Biasanya saya gunakan finddengan -exec, atau find . . .-print0 | while IFS= read -d'' FILENAME ; do . . . donestruktur untuk berurusan dengan nama file. Atau bisa digunakan python, saya sudah menambahkan contoh itu.
Sergiy Kolodyazhnyy
"$(ls)"selalu berkembang menjadi satu arg karena kutipan melindungi perluasan dari $(ls)pemisahan kata. Sama seperti mereka melindungi "$foo".
Peter Cordes
26

Tidak, itu tidak aman, dan alternatif yang umum digunakan rm *tidak jauh lebih aman.

Ada banyak masalah dengan rm $(ls). Seperti yang sudah dibahas orang lain dalam jawaban mereka, hasil dari lsakan dibagi pada karakter yang ada di pemisah bidang internal .

Skenario kasus terbaik, itu tidak bekerja. Skenario kasus terburuk, Anda bermaksud menghapus hanya file (tapi bukan direktori) - atau secara selektif menghapus beberapa file -i- tetapi ada file dengan nama c -rfdi direktori saat ini. Mari lihat apa yang terjadi.

$ mkdir a
$ touch b
$ touch 'c -rf'
$ rm -i $(ls)
$ ls
c -rf

Perintah rm -i $(ls)itu seharusnya menghapus hanya file dan bertanya sebelum menghapus masing-masing, tetapi perintah yang akhirnya dijalankan dibaca

rm -i a b c -rf

jadi itu melakukan sesuatu yang sama sekali berbeda.

Perhatikan bahwa rm *hanya sedikit lebih baik. Dengan struktur direktori seperti sebelumnya, itu akan berperilaku seperti yang dimaksudkan di sini, tetapi jika Anda memiliki file yang dipanggil -rf, Anda masih kurang beruntung.

$ mkdir a
$ touch b
$ touch ./-rf
$ rm -i *
$ ls
-rf

Ada beberapa alternatif yang lebih baik. Yang paling mudah hanya melibatkan rm dan globbing.

  • Perintah

    rm -- *
    

    akan bekerja persis seperti yang dimaksudkan, di mana --sinyal bahwa segala sesuatu setelahnya tidak harus ditafsirkan sebagai opsi.

    Ini telah menjadi bagian dari pedoman sintaks utilitas POSIX selama lebih dari dua dekade sekarang. Ini tersebar luas, tetapi Anda seharusnya tidak mengharapkannya hadir di mana-mana.

  • Perintah

    rm ./*
    

    membuat bola berkembang secara berbeda dan karenanya tidak memerlukan dukungan dari utilitas yang disebut.

    Untuk contoh saya dari atas, Anda dapat melihat perintah yang pada akhirnya akan dieksekusi oleh prepending echo .

    $ echo rm ./*
    rm ./a ./b ./-rf
    

    Yang utama ./mencegah rm dari secara tidak sengaja memperlakukan salah satu nama file seperti opsi.

Dennis
sumber
1
Poin yang sangat bagus, nama file dengan - setelah ekspansi menjadi bendera untuk rm. +1
Sergiy Kolodyazhnyy
1
Bahkan mengerikan: touch 'foo -rf .. bar. Saya tidak berpikir penyerang bisa mendapatkan yang lebih tinggi dari direktori induk, kecuali kita dapat menghasilkan pemisah jalur dalam lsoutput.
Peter Cordes
Penyerang @Peter harus memiliki izin menulis untuk menghapus direktori paten di tempat pertama, bukan?
Sergiy Kolodyazhnyy
1
@PeterCordes Saya tidak yakin apakah semua versi rm memiliki failafe ini, tetapi pada Ubuntu, openSUSE, dan Fedora, dikatakan rm: refusing to remove '.' or '..' directory: skipping '..'atau sesuatu yang serupa ketika mencoba untuk menghapus direktori induk.
Dennis
@Erg: penyerang hanya mengirimkan Anda. Zip dengan nama file di dalamnya dan memungkinkan Anda menembak diri Anda di kaki dengan mengekstraksi dan kemudian mencoba menghapus isinya. Atau dengan membuat nama file di / var / tmp atau apalah.
Peter Cordes