Есть ли недостатки в использовании rm $ (ls) для удаления файлов?

13

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

posixKing
источник
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после подстановки, что лишает цели удаления имени файла.

Что работает

Вы хотите удалить файлы в текущем каталоге. Так что используйте 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)"пример работает, только если в каталоге только один файл. Вы также можете просто использовать табуляцию для расширения имени файла, так как это единственное завершение.
Питер Кордес
@PeterCordes действительно, по странной причине, он работает только с одним файлом. Это еще один аргумент против использования lsтогда :) Я отредактирую это
Сергей Колодяжный
Я бы не назвал это странной причиной: вы либо цитируете, $(ls)чтобы отключить разделение слов, либо позволяете разделять слова (что приводит к катастрофическим результатам). Единственный чистый способ передать список из нескольких строк без обработки данных как кода - это использовать переменные массива или \0разделитель, но сама оболочка не может этого сделать. По-прежнему IFS=$'\n'наименее опасен, но не может сравниться find -print0 | xargs -0. Или grep -l --null. Или вы избегаете всей проблемы с такими вещами, как find -exec rm {} +. (Обратите внимание, +чтобы передать несколько аргументов для каждого вызова rm; ПУТЬ более эффективен).
Питер Кордес
@PeterCordes Да, полностью согласен там. Но 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 и globbing.

  • Команда

    rm -- *
    

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

    Это было частью рекомендаций по синтаксису утилит POSIX уже более двух десятилетий. Это широко распространено, но вы не должны ожидать, что оно будет присутствовать везде.

  • Команда

    rm ./*
    

    заставляет глобус расширяться по-другому и поэтому не требует поддержки от вызываемой утилиты.

    Для моего примера, приведенного выше, вы можете увидеть команду, которая в конечном итоге будет выполнена путем добавления echo .

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

    Ведущий ./предотвращает случайную обработку rm любого из имен файлов как параметров.

Деннис
источник
1
Очень хороший момент, имена файлов с - после расширения становятся флагами для rm. +1
Сергей Колодяжный
1
Еще противнее touch 'foo -rf .. bar. Я не думаю, что злоумышленник может получить более высокий уровень, чем родительский каталог, если мы не сможем создать разделитель пути в lsвыводе.
Питер Кордес
Злоумышленник @Peter должен был бы иметь права на запись, чтобы удалить каталог патентов, не так ли?
Сергей Колодяжный
1
@PeterCordes Я не уверен, что все версии rm имеют такую ​​отказоустойчивость, но в Ubuntu, openSUSE и Fedora это говорит rm: refusing to remove '.' or '..' directory: skipping '..'или что-то подобное при попытке удалить родительский каталог.
Деннис
@Serg: злоумышленник просто отправляет вам ZIP-файл с этим именем файла и позволяет вам выстрелить себе в ногу, извлекая его, а затем пытаясь удалить содержимое. Или создав это имя в / var / tmp или что-то в этом роде.
Питер Кордес