Удалить все файлы, кроме файлов с определенным расширением

Это удалит все файлы, которые заканчиваются на .a или .b.

$ ls *.a
a.a  b.a  c.a

$ ls *.b
a.b  b.b  c.b

$ rm *.a *.b

Как мне сделать наоборот и удалить все файлы, которые заканчиваются на *.*, кроме тех, которые заканчиваются на *.a и *.b?


person HattrickNZ    schedule 05.06.2014    source источник
comment
возможный дубликат rm всех файлов, кроме некоторых   -  person savanto    schedule 05.06.2014
comment
аналогично здесь используйте shopt -u extglob для отключения   -  person HattrickNZ    schedule 11.06.2014


Ответы (4)


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

Вероятно, самый простой подход в вашем случае (упрощенная версия https://stackoverflow.com/a/10448940/45375 ):

 (GLOBIGNORE='*.a:*.b'; rm *.*)
  • Обратите внимание на использование подоболочки ((...)) для локализации установки переменной GLOBIGNORE.
  • Шаблоны, назначенные GLOBIGNORE, должны быть разделены :.

Привлекательность этого подхода заключается в том, что вы можете использовать одну подоболочку без изменения глобального состояния.

Напротив, использование одной подоболочки с помощью shopt -s extglob требует некоторой хитрости:

(shopt -s extglob; glob='*.!(a|b)'; echo $glob)

Обратите внимание на обязательное использование промежуточной переменной, без которой команда сломалась бы (поскольку литерал подстановки был бы расширен ДО выполнения команд, и в этот момент расширенный синтаксис подстановки еще не распознан).


Предупреждение: использование GLOBIGNORE имеет неожиданный побочный эффект (ошибка?):

Если для GLOBIGNORE установлено любое значение, расширение имени пути * и *.* ведет себя так, как если бы действовала опция оболочки dotglob, даже если это не так.
Другими словами: если GLOBIGNORE установлен, скрытые файлы, не исключенные явным образом шаблоном в GLOBIGNORE, всегда сопоставляются * и *.*.

dotglob по умолчанию выключено, поэтому * НЕ включает скрытые файлы (если GLOBIGNORE не установлено, что по умолчанию верно).

Если вы также хотите исключить скрытые файлы при использовании GLOBIGNORE, добавьте следующий шаблон: .*; применительно к вопросу, вы получите:

 (GLOBIGNORE='*.a:*.b:.*'; rm *.*)

Напротив, использование расширенного подстановки после включения параметра оболочки extglob ДЕЙСТВИТЕЛЬНО учитывает параметр dotglob.

person mklement0    schedule 05.06.2014

Вы можете включить расширенный глобус в bash:

shopt -s extglob

Затем вы можете использовать:

rm *.!(a|b)

Чтобы удалить все файлы, которые заканчиваются на *.*, кроме тех, которые заканчиваются на *.a ИЛИ *.b

Обновление: (спасибо @mklement0) Вот способ локализовать настройку extglob (без изменения глобального состояния), выполнив это в subshell и используя промежуточную переменную:

(shopt -s extglob; glob='*.!(a|b)'; rm $glob)
person anubhava    schedule 05.06.2014
comment
Вдохновленный комментарием @savanto, я нашел способ локализировать настройку extglob (т. е. не изменять глобальное состояние) с помощью подоболочки: (shopt -s extglob; glob='*.!(a|b)'; rm $glob) — обратите внимание на обязательное использование промежуточной переменной . - person mklement0; 05.06.2014

Есть некоторые оболочки, которые на это способны (думаю?), однако bash по умолчанию нет. Если вы используете bash на Cygwin, вы можете сделать это:

rm $(ls -1 | grep -v '.*\.a' | grep -v '.*\.b')
  • ls -1 (это один) перечислить все файлы в текущем каталоге по одному в строке.
  • grep -v '.*\.a' возвращает все совпадения, которые не заканчиваются на .a
  • grep -v '.*\.b' возвращает все совпадения, которые не заканчиваются на .b
person savanto    schedule 05.06.2014
comment
tks, надеялся использовать переменную, чтобы я мог добавить другие критерии, которые нельзя удалять. - person HattrickNZ; 05.06.2014
comment
@HattrickNZ См. этот ответ из другого сообщения. Ваш bash может поддерживать синтаксис этого стиля с включенным extglob: rm !(*.a|*.b) в этом случае вы можете использовать переменную. - person savanto; 05.06.2014
comment
@HattrickNZ На самом деле, я могу убедиться, что это работает: shopt -s extglob; rm_all_but="*.a|*.b"; rm !($rm_all_but). Имейте в виду, что extglob останется включенным... - person savanto; 05.06.2014
comment
надо поискать extglob - person HattrickNZ; 05.06.2014
comment
Спасибо за то, что придумали технику промежуточных переменных, позволяющую помещать shopt -s extglob и использовать расширенный глобус в одной строке. Если вы поместите это в вложенную оболочку, параметр extglob будет эффективно локализован (не останется включенным): (shopt -s extglob; glob='!(*.a|*.b)'; rm $glob). - person mklement0; 05.06.2014
comment
@mklement0 Это круто! Но есть ли причина не оставлять extglob включенным? По умолчанию он отключен, поэтому я так и оставил. Будет ли это мешать мне? - person savanto; 05.06.2014
comment
Обычно я бы сказал, что проблематично изменить состояние global только для поддержки одной операции. Код, выполняемый позже, может сделать предположение, что глобальное состояние не изменилось. В этом конкретном случае, если оставить extglob включенным, больше строк будут действительными глобусами, поэтому — по крайней мере, гипотетически — то, что считается строковым литералом с выключенным extglob, может неожиданно превратиться в шаблон с включенным. - person mklement0; 05.06.2014

Иногда лучше не настаивать на решении проблемы определенным образом. И для общей проблемы «действия на определенные файлы, которые должны быть определены каким-то хитрым способом», find, вероятно, является лучшим универсальным инструментом, который вы найдете.

find . -type f -maxdepth 1 ! -name \*.[ab] -delete

Опустите -maxdepth 1, если вы хотите перейти в подкаталоги.

person DevSolar    schedule 05.06.2014