Unix - удалять файлы и папки, кроме PATTERN

10

Недавно я столкнулся с задачей удаления всех файлов / папок в каталоге, кроме тех, которые соответствуют определенному шаблону. Поэтому я подготовил однострочную команду Unix для выполнения этой работы. Это должна быть только одна строка? Полагаю, нет, но это определенно круче!

Хотя проблема довольно проста, я был немного удивлен тем, насколько сложным оказалось мое решение. Вот команда, которую я использовал; ПРИМЕЧАНИЕ: это плохое решение, потому что оно не обрабатывает имена файлов, содержащие символы перевода строки (что не имело значения в моей ситуации).

ls | grep -v PATTERN | xargs -n1 -IREPLACE rm -rf REPLACE

Я не использовал команду «найти», потому что я не хочу копировать в папки, соответствующие PATTERN. Например, рассмотрим следующую файловую структуру:

file_foo.txt
first_dir
  |
  +--> contents
  +--> ...
foo_dir
  |
  +--> anotherfile.txt
  +--> morefiles.log
foo_file.txt
somefile.txt

При использовании шаблона "foo" необходимо удалить только "first_dir" (и, конечно, его содержимое) и "somefile.txt" ( не "anotherfile.txt" или "morefiles.log").

Вопрос, есть ли лучшие (более элегантные и правильные) способы сделать это?


РЕДАКТИРОВАТЬ:
Недавно было доведено до моего сведения, что «найти» может быть лучшим вариантом:

find * -maxdepth 0 ! -name PATTERN -print0 | xargs -0n1 rm -rf

Это решение правильно обрабатывает пути, содержащие символы перевода строки.

joxl
источник
РЕДАКТИРОВАТЬ: изменил ошибочный "the_dir", чтобы исправить "first_dir".
Joxl

Ответы:

10

Следующие примеры имеют echoпрефикс, так что вы можете проверить шаблоны перед их фактическим использованием. Удалить, echoчтобы активировать rm -rf. Заменить, rm -riчтобы запросить подтверждение.

ksh имеет расширение с отрицательным соответствием для его смещения:

# ksh
echo rm -rf !(*foo*)

Тот же синтаксис доступен в bash, если вы установите extglobопцию:

# bash
shopt -s extglob
echo rm -rf !(*foo*)

У zsh есть собственный синтаксис для этого:

# zsh
setopt extended_glob
echo rm -rf ^*foo*

Он также может использовать синтаксис стиля ksh :

# zsh: ksh-style glob syntax
setopt ksh_glob no_bare_glob_qual
echo rm -rf !(*foo*)

# zsh: ksh-style glob syntax, adapted for the default bare_glob_qual option
setopt ksh_glob bare_glob_qual
echo rm -rf (!(*foo*))
Крис Джонсен
источник
Простой, элегантный, обрабатывает символы перевода строки, (почти) одну строку (я добавил shopt -s extglobв мой файл .bashrc, так что теперь он является однострочным). Отлично! Я обещаю поднять голос, как только моя репутация позволяет это.
Joxl
7

Вот еще одно findрешение. Я не уверен, что это имеет какое-то реальное преимущество перед вашим, но это не нужно xargsи учитывает редкую возможность, * расширяющуюся до слишком большого количества имен.

find . -maxdepth 1 ! -name PATTERN -type f -delete

Я также добавил, -type fчто он не будет пытаться удалить каталоги.

Предупреждение: -deleteэто мощно. Я дал одному из моих тестовых файлов 0 разрешений, и команда выше удалила его без колебаний.

garyjohn
источник
6
Проверьте сначала. Замените -deleteна, -printчтобы увидеть, находит ли он нужные вам файлы. Если все хорошо, используйте историю команд (например, нажмите кнопку вверх), чтобы вернуться к предыдущей команде, чтобы убедиться, что вы работаете с теми же фильтрами поиска.
Даг Харрис
Читайте внимательно, обратите внимание, что я действительно хочу удалить каталоги. И find -deleteне удалит непустые каталоги. Также обратите внимание, что find -maxdepth 1соответствует "." (текущий каталог), что действительно плохо. Хотя мне нравится ваша идея устранения xargs, можно использовать find -execальтернативно. find * -maxdepth 0 ! -name PATTERN -exec rm -rf '{}' \;
Joxl
0

Вместо этого вы можете использовать этот пример сценария реболя http://askcodegeneration.com/keep-subversion/, который гораздо более понятен и может управлять взаимодействием с пользователем, таким как выбор и подтверждение папки:

delete-file: func[file][
    if/else none? find file "/.svn/" [
        delete file
    ][
        print ["keeping" file]
    ]
]
dir: request-dir
ans: ask rejoin ["Do you confirm " dir "? (Yes): "]
if (ans = "Yes") [
    foreach-file dir :delete-file
]
Rebol Tutorial
источник