У меня есть несколько тысяч файлов в формате filename.12345.end. Я хочу сохранить только каждый 12-й файл, поэтому file.00012.end, file.00024.end ... file.99996.end и удалите все остальное.
Файлы также могут иметь номера ранее в своем имени файла и обычно имеют вид: file.00064.name.99999.end
Я использую оболочку Bash и не могу понять, как перебрать файлы, а затем получить номер и проверить, удаляет ли он number%%12=0
файл, если нет. Может кто-нибудь мне помочь?
Спасибо дорина
Ответы:
Вот решение Perl. Это должно быть намного быстрее для тысяч файлов:
Который может быть далее сокращен в:
Если у вас слишком много файлов и вы не можете использовать простые
*
, вы можете сделать что-то вроде:Что касается скорости, вот сравнение этого подхода и оболочки, предоставленной в одном из других ответов:
Как видите, разница огромна, как и ожидалось .
объяснение
-e
просто говоритperl
запустить скрипт, указанный в командной строке.@ARGV
это специальная переменная, содержащая все аргументы, данные скрипту. Поскольку мы даем его*
, он будет содержать все файлы (и каталоги) в текущем каталоге.grep
Будет искать через список имен файлов и искать любой , которые соответствуют строке чисел, точка иend
(/(\d+)\.end/)
.Поскольку числа (
\d
) находятся в группе захвата (круглые скобки), они сохраняются как$1
. Поэтомуgrep
он проверит, является ли это число кратным 12, и, если это не так, будет возвращено имя файла. Другими словами, массив@bad
содержит список файлов, которые будут удалены.Затем передается список, в
unlink()
который удаляются файлы (но не каталоги).источник
Учитывая, что ваши имена файлов в формате
file.00064.name.99999.end
, нам сначала нужно обрезать все, кроме нашего номера. Мы будем использоватьfor
цикл, чтобы сделать это.Мы также должны указать оболочке Bash использовать основание 10, потому что арифметика Bash будет обрабатывать их числа, начинающиеся с 0, как основание 8, что приведет нас к путанице.
В качестве скрипта, запускаемого в каталоге, содержащем файлы, используйте:
Или вы можете использовать эту очень длинную уродливую команду, чтобы сделать то же самое:
Чтобы объяснить все части:
for f in ./*
означает для всего в текущем каталоге, do .... Это устанавливает каждый файл или каталог, найденный как переменная $ f.if [[ -f "$f" ]]
проверяет, является ли найденный элемент файлом, в противном случае мы переходим кecho "$f is not...
части, что означает, что мы не начинаем удаление каталогов случайно.file="${f%.*}"
устанавливает переменную $ file в качестве имени файла, обрезая все, что идет после последнего.
.if [[ $((10#${file##*.} % 12)) -eq 0 ]]
это где основная арифметика вступает в силу.${file##*.}
Обрезает все до последнего.
в нашем имени файла без расширения.$(( $num % $num2 ))
является синтаксисом арифметики Bash для использования операции по модулю, а10#
в начале Bash указывает Bash использовать 10, чтобы справиться с этими надоедливыми ведущими нулями.$((10#${file##*.} % 12))
затем оставляет нам остаток от нашего имени файла, разделенный на 12.-ne 0
проверяет, не равен ли остаток «нулю».rm
командой, вы можете заменитьrm
сecho
при первом запуске этого, чтобы убедиться , что вы получите ожидаемые файлы для удаления.Это решение не является рекурсивным, это означает, что оно будет обрабатывать только файлы в текущем каталоге и не попадать ни в какие подкаталоги.
if
Заявление сecho
командой , чтобы предупредить о каталогах не действительно необходимо , так какrm
на его собственном будет жаловаться каталогами, а не удалять их, так:Или
Будет работать правильно тоже.
источник
rm
несколько тысяч раз можно довольно медленно. Я предлагаюecho
имя файла вместо и трубы на выходе контура наxargs rm
(варианты добавлений по мере необходимости):for f in *; do if ... ; then echo "$f"; fi; done | xargs -rd '\n' -- rm --
.xargs
версия заняла 5 минут 1 секунду. Может ли это быть связано с накладными расходами наecho
@DavidFoerster?time { for f in *; do echo "$f"; done | xargs rm; }
1m11.450s / 0m10.695s / 0m16.800s сtime { for f in *; do rm "$f"; done; }
tmpfs. Bash v4.3.11, ядро v4.4.19.Вы можете использовать расширение скобок Bash для генерации имен, содержащих каждое 12-ое число. Давайте создадим некоторые тестовые данные
Тогда мы можем использовать следующее
Работает безнадежно медленно для большого количества файлов - для создания тысяч имен требуются время и память, так что это скорее хитрость, чем реальное эффективное решение.
источник
Немного долго, но это то, что пришло мне в голову.
Пояснение: Удалить каждый 12-й файл одиннадцать раз.
источник
При всей скромности я думаю, что это решение намного лучше, чем другой ответ:
Небольшое объяснение: сначала мы генерируем список файлов с
find
. Мы получаем все файлы, чьи имена заканчиваются.end
и находятся на глубине 1 (то есть они находятся непосредственно в рабочем каталоге, а не в каких-либо подпапках. Вы можете оставить это, если нет подпапок). Список вывода будет отсортирован в алфавитном порядке.Затем мы перенаправляем этот список
awk
туда, где используем специальную переменную,NR
которая является номером строки. Мы пропускаем каждый 12-й файл, печатая файлы гдеNR%12 != 0
. Командаawk
может быть сокращена доawk 'NR%12'
, потому что результат оператора по модулю интерпретируется как логическое значение, и в{print}
любом случае выполняется неявно.Итак, теперь у нас есть список файлов, которые нужно удалить, что мы можем сделать с помощью xargs и rm.
xargs
запускает данную команду (rm
) со стандартным вводом в качестве аргументов.Если у вас много файлов, вы получите сообщение об ошибке: «слишком длинный список аргументов» (на моей машине это ограничение равно 256 кБ, а POSIX требует минимум 4096 байт). Этого можно избежать с помощью
-n 100
флага, который разбивает аргументы на каждые 100 слов (не на строки, на что следует обращать внимание, если в именах файлов есть пробелы) и выполняет отдельнуюrm
команду, каждая из которых содержит только 100 аргументов.источник
-depth
должно быть раньше-name
; II) это не удастся, если любое из имен файлов содержат пробелы; iii) вы предполагаете, что файлы будут перечислены в порядке возрастания номеров (это то, что выawk
проверяете), но это почти наверняка не так. Следовательно, это приведет к удалению случайного набора файлов.-depth
. Тем не менее, это была наименьшая из проблем, самая важная из них - это то, что вы удаляете случайный набор файлов, а не те, которые хочет ОП.-depth
не имеет значения и делает противоположное тому, что вы думаете. Смотритеman find
: «-depth Обрабатывает содержимое каждого каталога до самого каталога». Таким образом, это фактически сойдет в подкаталоги и приведет к хаосу повсюду.-depth n
и так и-maxdepth n
существует. Для первого требуется, чтобы глубина была ровно n, а для второго она может быть <= n. II). Да, это плохо, но для этого конкретного примера это не имеет значения. Вы можете исправить этоfind ... -print0 | awk 'BEGIN {RS="\0"}; NR%12 != 0' | xargs -0 -n100 rm
, используя нулевой байт в качестве разделителя записей (что недопустимо в именах файлов). III) Еще раз, в этом случае предположение разумно. В противном случае вы можете вставитьsort -n
междуfind
иawk
или или перенаправитьfind
в файл и отсортировать его, как вам нравится.find
. Опять же, однако, основная проблема заключается в том, что вы предполагаете, чтоfind
возвращает отсортированный список. Это не так.Для использования только bash мой первый подход заключается в следующем: 1. переместить все файлы, которые вы хотите сохранить, в другой каталог (т. Е. Все те, чье число в имени файла кратно 12), затем 2. удалить все оставшиеся файлы в каталоге, затем 3. поместите несколько файлов из 12, которые вы сохранили, туда, где они были. Так что-то вроде этого может работать:
источник
filename
часть, если она не соответствует?