У меня есть несколько тысяч файлов в формате filename.12345.end. Я только хочу сохранить каждый 12-й файл, так файл 00012.end, файл 00024.end... файл 99996.end и удалить все остальное.
Файлы могут также иметь числа ранее в их имени файла и обычно имеют форму: file.00064.name.99999.end
Я использую оболочку Bash и не могу выяснить, как циклично выполниться по файлам и затем вынуть число и проверить, является ли это number%%12=0
удаление файла, если нет. Кто-либо может помочь мне?
Спасибо, Dorina
Можно использовать расширение скобки Bash для генерации имен, содержащих каждое 12-е число. Давайте создадим некоторые данные тестирования
$ touch file.{0..9}{0..9}{0..9}{0..9}{0..9}.end # create test data
$ mv file.00024.end file.00024.end.name.99999.end # testing this form of filenames
Затем, мы можем использовать следующий
$ ls 'file.'{00012..100..12}* # print these with numbers less than 100
file.00012.end file.00036.end file.00060.end file.00084.end
file.00024.end.name.99999.end file.00048.end file.00072.end file.00096.end
$ rm 'file.'{00012..100000..12}* # do the job
, Работы безнадежно замедляются для большой суммы файлов, хотя - это занимает время и память для генерации тысяч имен - таким образом, это - больше прием что фактическое эффективное решение.
Немного долго, но то, что прибыло по моему мнению.
for num in $(seq 1 1 11) ; do
for sequence in $(seq -f %05g $num 12 99999) ; do
rm file.$sequence.end.99999;
done
done
Объяснение: Удалите каждый 12-й файл одиннадцать раз.
Во всей скромности я думаю, что это решение намного более хорошо, чем другой ответ:
find . -name '*.end' -depth 1 | awk 'NR%12 != 0 {print}' | xargs -n100 rm
Немного объяснения: Сначала мы генерируем список файлов с find
. Мы получаем все файлы, имя которых заканчивается .end
и которые являются на глубине 1 (то есть, они находятся непосредственно в рабочем каталоге а не в любых подпапках. Можно пропустить это, при отсутствии подпапок). Выходной список будет отсортирован в алфавитном порядке.
Затем мы передаем тот список по каналу в awk
, где мы используем специальную переменную NR
, который является номером строки. Мы не учитываем каждый 12-й файл путем печати файлов где NR%12 != 0
. Эти awk
команда может быть сокращена к awk 'NR%12'
, потому что результат оператора по модулю интерпретируется, поскольку булево значение и эти {print}
неявно сделано так или иначе.
Поэтому теперь у нас есть список файлов, которые должны быть удалены, который мы можем сделать с xargs и комнатой xargs
выполнения данная команда (rm
) со стандартным входом как аргументы.
, Если у Вас есть много файлов, Вы получите ошибку при высказывании чего-то как 'список аргументов слишком долго' (на моей машине, что предел составляет 256 КБ, и минимум, требуемый POSIX, составляет 4 096 байтов). Этого можно избежать эти -n 100
флаг, который разделяет аргументы каждые 100 слов (не строки, что-то, чтобы не упустить, если Ваши имена файлов имеют пробелы), и выполняет отдельное rm
команда, каждый только с 100 аргументами.
Поскольку использование только колотит, мой первый подход был бы к: 1. переместите все файлы, которые Вы хотите сохранить в другой каталог (т.е. все те, число которых в имени файла является кратным 12), затем 2. удалите все остающиеся файлы в каталоге, затем 3. поместите multiple-12 файлы, от которых Вы воздержались, где они были. Таким образом, что-то вроде этого могло бы работать:
cd dir_containing_files
mkdir keep_these_files
n=0
while [ "${n}" -lt 99999 ]; do
padded_n="`echo -n "00000${n}" | tail -c 5`"
mv "filename${padded_n}.end" keep_these_files/
n=$[n+12]
done
rm filename*.end
mv keep_these_files/* .
rmdir keep_these_files
Вот решение для Perl. Это должно быть намного быстрее для тысяч файлов:
perl -e '@bad=grep{/(\d+)\.end/ && $1 % 12 != 0}@ARGV; unlink @bad' *
Который может быть далее сжат в:
perl -e 'unlink grep{/(\d+)\.end/ && $1 % 12 != 0}@ARGV;' *
Если Вы имеете слишком много файлов и не можете использовать простое *
, можно сделать что-то как:
perl -e 'opendir($d,"."); unlink grep{/(\d+)\.end/ && $1 % 12 != 0} readdir($dir)'
Что касается скорости, вот является сравнение этого подхода и оболочки одним обеспеченным в одном из других ответов:
$ touch file.{01..64}.name.{00001..01000}.end
$ ls | wc
64000 64000 1472000
$ time for f in ./* ; do file="${f%.*}"; if [[ $((10#${file##*.} % 12)) -ne 0 ]]; then rm "$f"; fi; done
real 2m44.258s
user 0m9.183s
sys 1m7.647s
$ touch file.{01..64}.name.{00001..01000}.end
$ time perl -e 'unlink grep{/(\d+)\.end/ && $1 % 12 != 0}@ARGV;' *
real 0m0.610s
user 0m0.317s
sys 0m0.290s
Как Вы видите, разница огромна, как ожидалось.
-e
просто говорит perl
запускать скрипт, данный на командной строке. @ARGV
специальная переменная, содержащая все аргументы, данные сценарию. Так как мы даем его *
, это будет содержать все файлы (и каталоги) в текущем каталоге. grep
будет перерывать список имен файлов и искать любой, который соответствует строке чисел, точки и end
(/(\d+)\.end/)
.
Поскольку числа (\d
) находятся в группе получения (круглые скобки), они сохраняются как $1
. Так grep
затем проверит, является ли то число кратным 12 и, если это не, имя файла будет возвращено. Другими словами, массив @bad
содержит список файлов, которые будут удалены.
Список затем передается unlink()
который удаляет файлы (но не каталоги).
Учитывая, что Ваши имена файлов находятся в формате file.00064.name.99999.end
, мы сначала должны отрезать все кроме нашего числа. Мы будем использовать a for
цикл, чтобы сделать это.
Мы также должны сказать оболочке Bash использовать основу 10, потому что арифметика Bash будет рассматривать их числа, начинающиеся с 0 как основа 8, который испортит вещи для нас.
Как сценарий, чтобы быть запущенным, когда в каталоге, содержащем использование файлов:
#!/bin/bash
for f in ./*
do
if [[ -f "$f" ]]; then
file="${f%.*}"
if [[ $((10#${file##*.} % 12)) -ne 0 ]]; then
rm "$f"
fi
else
echo "$f is not a file, skipping."
fi
done
Или можно использовать эту очень длинную ужасную команду, чтобы сделать то же самое:
for f in ./* ; do if [[ -f "$f" ]]; then file="${f%.*}"; if [[ $((10#${file##*.} % 12)) -ne 0 ]]; then rm "$f"; fi; else echo "$f is not a file, skipping."; fi; done
Объяснить все части:
for f in ./*
средства для всего в текущем каталоге, сделать.... Это устанавливает каждый файл или каталог, найденный как переменный $f.if [[ -f "$f" ]]
проверки, является ли найденный объект файлом, если не мы пропускаем к echo "$f is not...
часть, что означает, что мы не начинаем удалять каталоги случайно.file="${f%.*}"
устанавливает переменную $file как обрезку имени файла от того, что прибывает после последнего .
.if [[ $((10#${file##*.} % 12)) -eq 0 ]]
то, где основная Арифметика умирает. ${file##*.}
обрезки все перед последним .
в нашем имени файла без расширения. $(( $num % $num2 ))
синтаксис для арифметики Bash для использования операции по модулю, 10#
в запуске говорит Bash использовать основу 10, иметь дело с теми противное продвижение 0s. $((10#${file##*.} % 12))
затем оставляет нас остатком от нашего числа имен файлов разделенный на 12. -ne 0
проверки, не равен ли остаток "" для обнуления.rm
команда, можно хотеть заменить rm
с echo
сначала выполняя это, чтобы проверить, что Вы заставляете ожидаемые файлы удалять.Это решение нерекурсивно, означая, что оно только обработает файлы в текущем каталоге, оно не войдет ни в какие подкаталоги.
if
оператор с echo
команда для предупреждения о каталогах не действительно необходима как rm
на своем собственном будет жаловаться на каталоги и не удалять их, таким образом:
#!/bin/bash
for f in ./*
do
file="${f%.*}"
if [[ $((10#${file##*.} % 12)) -ne 0 ]]; then
rm "$f"
fi
done
Или
for f in ./* ; do file="${f%.*}"; if [[ $((10#${file##*.} % 12)) -ne 0 ]]; then rm "$f"; fi; done
Будет работать правильно также.