Чи є недоліки використання rm $ (ls) для видалення файлів?


13

Мені було цікаво, чи безпечне використання rm $(ls)для видалення файлів (або rm -r $(ls)для видалення каталогів)? Тому що на всіх веб-сайтах люди дають інші способи зробити це, хоча ця команда здається набагато простішою, ніж інші команди.


3
Основна відповідь, ні. Я не можу обробляти спеціальні символи. Я можу написати відповідь трохи, щоб пояснити це детальніше
Сергій Колодяжний

5
touch 'foo -r .. bar'; rm $(ls); куди пішов мій батьківський каталог? Крім того, які альтернативи ви бачите, що є ще складнішими за цю? rm *набагато простіше набрати та подумати і безпечніше (але не зовсім безпечно; див. відповідь Денніса).
Пітер Кордес

1
Окрім відмінних відповідей, пам’ятайте, що вони lsможуть відрізнятися між реалізаціями, а тому є нестандартними. Залежно від того, що вам потрібно, розгляньте такі альтернативи, як findі stat. Ви повинні використовувати lsлише для споживання людиною, ніколи не для використання іншими командами чи сценаріями.
Падді Ландау

Відповіді:


7

Що це призначено робити?

  • ls список файлів у поточному каталозі
  • $(ls)замінює вихід lsмісця, які є аргументомrm
  • По суті rm $(ls)призначений для видалення всіх файлів у поточному каталозі

Що не так з цією картиною?

lsне вдається правильно обробити спеціальні символи у імені файлу. Користувачі Unix, як правило, радили використовувати різні підходи . Я також це показав у пов'язаному питанні про підрахунок імен файлів . Наприклад:

$ 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після заміни, що перемагає мету видалити ім'я файлу.

Що працює

Ви хочете видалити файли в поточному каталозі. Тому використовуйте глобус 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)"приклад працює лише в тому випадку, якщо в каталозі є лише один файл. Ви також можете просто скористатися вкладкою для розширення імені файлу, оскільки це єдине завершення.
Пітер Кордес

@PeterCordes дійсно, з незвичайної причини він працює лише з одним файлом. Це ще один аргумент проти використання ls:) Я це відредагую
Сергій Колодяжний

Я б не називав це дивною причиною: ви або цитуєте, $(ls)щоб відключити поділ слів, або ви дозволите поділу слів (з катастрофічними результатами). Єдиний чистий спосіб пройти навколо списку декількох рядків без трактування даних як коду - зі змінними масиву або з \0роздільником, але сама оболонка не може цього зробити. Все ще IFS=$'\n'є найменш небезпечним, але не може відповідати find -print0 | xargs -0. Або grep -l --null. Або ви уникаєте всієї проблеми з такими речами find -exec rm {} +. (Зауважте, що +для передачі декількох аргументів до кожного виклику rm; БУДЕ ефективніше).
Пітер Кордес

@PeterCordes Yup, повністю згоден там. Але IFS=$'\n'не вдасться і в цьому випадку, оскільки я нову строку в імені файлу, тому розбивання слів розглядає її як дві назви файлів замість однієї. Однак дивним є той факт, що за замовчуванням, IFSякий є пробіл, вкладка, новий рядок, оригінал, rm "$(ls)"також має бути збій, він повинен трактувати так, як я сказав ім'я файлу як два окремих, але це не так. Зазвичай я використовую findз -execабо find . . .-print0 | while IFS= read -d'' FILENAME ; do . . . doneструктурою , щоб мати справу з іменами файлів. Або можна використати python, я додав приклад цього.
Сергій Колодяжний

"$(ls)"завжди розширюється на один аргумент, тому що лапки захищають розширення $(ls)від розбиття слів. Так само, як вони захищають "$foo".
Пітер Кордес

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 та глобулінг.

  • Команда

    rm -- *
    

    буде працювати саме так, як задумано, де --сигналізує, що все після нього не слід інтерпретувати як варіант.

    Це є частиною рекомендацій синтаксису утиліти POSIX вже понад два десятиліття. Він широко поширений, але не варто сподіватися, що він буде присутній всюди.

  • Команда

    rm ./*
    

    змушує глобус розширюватися по-різному, і тому не вимагає підтримки з боку утиліти.

    На моєму прикладі зверху ви можете побачити команду, яка в кінцевому рахунку буде виконана заздалегідь відлунням .

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

    Провідний ./заважає rm випадково обробити будь-яку з назв файлів, як опції.


1
Дуже хороший момент, назви файлів з - після розширення стають прапорами rm. +1
Сергій Колодяжний

1
Навіть неприємніше : touch 'foo -rf .. bar. Я не думаю, що зловмисник може отримати більше, ніж батьківський каталог, якщо ми не можемо створити роздільник шляху у lsвихідних даних.
Пітер Кордес

@ Петер-зловмисник повинен був би мати дозволи на запис в першу чергу для видалення патентного каталогу, ні?
Сергій Колодяжний

1
@PeterCordes Я не впевнений, чи всі версії rm мають цю безпечну помилку , але на Ubuntu, openSUSE та Fedora це говорить rm: refusing to remove '.' or '..' directory: skipping '..'або щось подібне при спробі видалити батьківський каталог.
Денніс

@Serg: зловмисник просто надсилає вам .zip з таким ім'ям файлу в ньому і дозволяє вам стріляти в ногу, витягуючи його, а потім намагаючись видалити вміст. Або створивши це ім'я файлу в / var / tmp або щось подібне.
Пітер Кордес
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.