มีข้อเสียของการใช้ rm $ (ls) เพื่อลบไฟล์หรือไม่?


13

ฉันสงสัยว่าถ้าใช้rm $(ls)เพื่อลบไฟล์ (หรือrm -r $(ls)เพื่อลบไดเรกทอรีด้วย) ปลอดภัยหรือไม่ เพราะในทุกเว็บไซต์ผู้คนให้วิธีอื่นในการทำสิ่งนี้แม้ว่าคำสั่งนี้จะดูเหมือนง่ายกว่าคำสั่งอื่น ๆ


3
คำตอบพื้นฐานหมายเลข ls ไม่สามารถจัดการกับอักขระพิเศษ ฉันสามารถเขียนคำตอบเพื่ออธิบายรายละเอียดเพิ่มเติมเล็กน้อย
Sergiy Kolodyazhnyy

5
touch 'foo -r .. bar'; rm $(ls); ไดเรกทอรีหลักของฉันไปอยู่ที่ไหน นอกจากนี้คุณเห็นว่ามีทางเลือกอะไรที่ซับซ้อนกว่านี้อีก? rm *พิมพ์และคิดได้ง่ายกว่าและปลอดภัยกว่า (แต่ไม่ปลอดภัยอย่างสมบูรณ์ดูคำตอบของเดนนิส)
Peter Cordes

1
นอกเหนือจากคำตอบที่ดีเยี่ยมโปรดทราบว่าlsอาจแตกต่างกันระหว่างการนำไปใช้งานและดังนั้นจึงไม่ใช่มาตรฐาน ทั้งนี้ขึ้นอยู่กับสิ่งที่คุณต้องพิจารณาทางเลือกเช่นและfind statคุณควรใช้lsเพื่อการบริโภคของมนุษย์เท่านั้นไม่ควรใช้คำสั่งอื่นหรือสคริปต์
Paddy Landau

คำตอบ:


7

สิ่งนี้ตั้งใจจะทำอย่างไร

  • ls แสดงรายการไฟล์ในไดเรกทอรีปัจจุบัน
  • $(ls)ทดแทนเอาต์พุตของlsตำแหน่งที่เป็นอาร์กิวเมนต์rm
  • rm $(ls)เป็นหลักมีวัตถุประสงค์เพื่อลบไฟล์ทั้งหมดในไดเรกทอรีปัจจุบัน

ภาพนี้มีอะไรผิดปกติ?

lsไม่สามารถจัดการอักขระพิเศษในชื่อไฟล์ได้อย่างถูกต้อง ผู้ใช้ระบบปฏิบัติการยูนิกซ์โดยทั่วไปแนะนำให้ใช้วิธีการที่แตกต่างกัน ฉันได้แสดงให้เห็นว่าในคำถามที่เกี่ยวข้องเกี่ยวกับชื่อไฟล์นับ ตัวอย่างเช่น

$ 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
$ 

นอกจากนี้ตามที่ระบุไว้อย่างถูกต้องในคำตอบของเดนิสชื่อไฟล์ที่มีเครื่องหมายขีดกลางสามารถตีความได้ว่าเป็นอาร์กิวเมนต์rmหลังจากการแทนที่ซึ่งเอาชนะวัตถุประสงค์ของการลบชื่อไฟล์

งานอะไร

คุณต้องการลบไฟล์ในไดเรกทอรีปัจจุบัน ดังนั้นใช้ 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
$ 

คุณสามารถใช้findคำสั่ง แนะนำให้ใช้เครื่องมือนี้บ่อย ๆ มากกว่าแค่ไดเรกทอรีปัจจุบันมันสามารถท่องไปทั่วทั้งไดเรกทอรีต้นไม้และเรียกใช้ไฟล์ผ่าน-exec . . .{} \;

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

Python ไม่มีปัญหาเกี่ยวกับอักขระพิเศษในชื่อไฟล์ดังนั้นเราสามารถใช้งานได้เช่นกัน (โปรดทราบว่าอันนี้มีไว้สำหรับไฟล์เท่านั้นคุณจะต้องใช้os.rmdir()และos.path.isdir()ถ้าคุณต้องการใช้งานในไดเรกทอรี):

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

ในความเป็นจริงคำสั่งข้างต้นสามารถเปลี่ยนเป็นฟังก์ชั่นหรือนามแฝงใน~/.bashrcเพื่อความกะทัดรัด ตัวอย่างเช่น,

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) ]'

}

รุ่น Perl ของที่จะเป็น

perl -e 'use Cwd;my $d=cwd();opendir(DIR,$d); while ( my $f = readdir(DIR)){ unlink $f;}; closedir(DIR)'

1
"$(ls)"ตัวอย่างของคุณจะใช้งานได้หากมีไฟล์เดียวในไดเรกทอรี คุณอาจใช้การเติมแท็บเพื่อขยายชื่อไฟล์ด้วยเช่นกัน
Peter Cordes

@PeterCordes แน่นอนด้วยเหตุผลแปลก ๆ มันใช้ได้กับไฟล์เดียวเท่านั้น นั่นเป็นอีกข้อโต้แย้งต่อการใช้lsแล้ว :) ฉันจะแก้ไขมันออกมา
Sergiy Kolodyazhnyy

ฉันจะไม่เรียกมันว่าเหตุผลแปลก ๆ : คุณอ้าง$(ls)ว่าปิดการใช้งานการแยกคำหรือคุณปล่อยให้การแยกคำเกิดขึ้น (พร้อมกับผลลัพธ์หายนะ) วิธีเดียวที่สะอาดในการส่งผ่านรายการของสตริงจำนวนมากโดยไม่ต้องดำเนินการกับข้อมูลในขณะที่โค้ดอยู่กับตัวแปรอาเรย์หรือด้วย\0ตัวคั่น แต่เชลล์เองไม่สามารถทำเช่นนั้นได้ ยังคงIFS=$'\n'เป็นอย่างน้อยอันตราย find -print0 | xargs -0แต่ไม่สามารถแข่งขันได้ grep -l --nullหรือ find -exec rm {} +หรือให้คุณหลีกเลี่ยงปัญหาทั้งกับสิ่งที่ต้องการ (โปรดสังเกตว่า+การส่ง args หลายรายการไปยังการเรียกใช้ rm แต่ละครั้งมีประสิทธิภาพมากขึ้น)
Peter Cordes

@PeterCordes Yup เห็นด้วยทั้งหมด แต่IFS=$'\n'จะล้มเหลวในกรณีนี้เช่นกันเนื่องจากฉันขึ้นบรรทัดใหม่ในชื่อไฟล์ดังนั้นการใช้คำจะถือว่าเป็นสองชื่อไฟล์แทนหนึ่ง อย่างไรก็ตามเหตุผลที่แปลกคือความจริงที่ว่าค่าปริยายIFSคือช่องว่างแท็บบรรทัดใหม่ต้นฉบับrm "$(ls)"ควรล้มเหลวควรปฏิบัติอย่างที่ฉันบอกว่าชื่อไฟล์เป็นสองแยก แต่ไม่ได้ โดยทั่วไปฉันใช้findกับ-execหรือfind . . .-print0 | while IFS= read -d'' FILENAME ; do . . . doneโครงสร้างเพื่อจัดการกับชื่อไฟล์ หรืออย่างใดอย่างหนึ่งสามารถใช้pythonฉันได้เพิ่มตัวอย่างของที่
Sergiy Kolodyazhnyy

"$(ls)"มักจะขยายเป็นหนึ่ง arg เนื่องจากเครื่องหมายคำพูดป้องกันการขยายตัวของ$(ls)การแยกคำ "$foo"เช่นเดียวกับพวกเขาปกป้อง
Peter Cordes

26

ไม่มันไม่ปลอดภัยและทางเลือกที่ใช้กันทั่วไปrm *ไม่ปลอดภัยกว่ามากนัก

มีหลายrm $(ls)ปัญหากับ เป็นคนอื่นได้กล่าวมาแล้วในคำตอบของพวกเขาส่งออกของlsจะถูกแบ่งตัวละครที่นำเสนอในคั่นข้อมูลภายใน

สถานการณ์กรณีที่ดีที่สุดมันไม่ทำงาน สถานการณ์กรณีที่เลวร้ายที่สุดคุณตั้งใจจะลบเฉพาะไฟล์ (แต่ไม่ใช่ไดเรกทอรี) - หรือเลือกบางไฟล์ด้วย-i- แต่มีไฟล์ที่มีชื่อc -rfในไดเรกทอรีปัจจุบัน มาดูกันว่าเกิดอะไรขึ้น

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

คำสั่งrm -i $(ls)ควรจะลบเฉพาะไฟล์และถามก่อนที่จะลบแต่ละไฟล์ แต่คำสั่งที่ถูกดำเนินการอ่านในท้ายที่สุด

rm -i a b c -rf

ดังนั้นจึงทำอย่างอื่นทั้งหมด

โปรดทราบว่าrm *ดีกว่าเล็กน้อย ด้วยโครงสร้างไดเรกทอรีก่อนหน้านี้มันจะทำงานตามที่ตั้งใจไว้ แต่ถ้าคุณมีไฟล์ชื่อ-rfคุณก็ยังโชคไม่ดี

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

มีทางเลือกที่ดีกว่าหลายประการ สิ่งที่ง่ายที่สุดเกี่ยวข้องกับrmและ globbing เท่านั้น

  • คำสั่ง

    rm -- *
    

    จะทำงานได้ตามที่ตั้งใจซึ่งเป็น--สัญญาณว่าทุกอย่างหลังจากนั้นไม่ควรถูกตีความว่าเป็นตัวเลือก

    นี่เป็นส่วนหนึ่งของแนวทางไวยากรณ์ของโปรแกรมอรรถประโยชน์ POSIX เป็นเวลากว่าสองทศวรรษแล้ว มันแพร่หลาย แต่คุณไม่ควรคาดหวังว่ามันจะมีอยู่ทุกหนทุกแห่ง

  • คำสั่ง

    rm ./*
    

    ทำให้ glob ขยายตัวแตกต่างกันดังนั้นจึงไม่ต้องการการสนับสนุนจากยูทิลิตี้ที่เรียกว่า

    สำหรับตัวอย่างของฉันจากข้างต้นคุณสามารถดูคำสั่งที่ในที่สุดจะได้รับการดำเนินการโดย prepending ก้อง

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

    ชั้นนำ./ป้องกันrmจากการรักษาชื่อไฟล์ใด ๆ เช่นตัวเลือกโดยไม่ตั้งใจ


1
จุดที่ดีมากกับชื่อไฟล์ - rmหลังจากที่ขยายตัวกลายเป็นธงที่จะ +1
Sergiy Kolodyazhnyy

1
แม้แต่น่ารังเกียจ: touch 'foo -rf .. bar. ฉันไม่คิดว่าผู้โจมตีสามารถรับสูงกว่าไดเรคทอรีหลักได้เว้นแต่ว่าเราสามารถสร้างตัวแยกพา ธ ในlsเอาต์พุตของ
Peter Cordes

@Peter ผู้โจมตีจะต้องมีสิทธิ์ในการเขียนเพื่อลบไดเรกทอรีสิทธิบัตรตั้งแต่แรกใช่ไหม?
Sergiy Kolodyazhnyy

1
@PeterCordes ฉันไม่แน่ใจว่าrmทุกเวอร์ชันมีสิ่งที่ไม่ปลอดภัยหรือไม่ แต่ใน Ubuntu, openSUSE และ Fedora มันพูดrm: refusing to remove '.' or '..' directory: skipping '..'หรือสิ่งที่คล้ายกันเมื่อพยายามลบไดเรกทอรีหลัก
Dennis

@Serg: ผู้โจมตีเพียงส่ง. zip ด้วยชื่อไฟล์ในนั้นและให้คุณยิงตัวเองโดยการแตกไฟล์แล้วพยายามลบเนื้อหา หรือโดยการสร้างชื่อไฟล์นั้นใน / var / tmp หรืออะไรบางอย่าง
Peter Cordes
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.