Удалите все кроме каждого 12-го файла

У меня есть несколько тысяч файлов в формате filename.12345.end. Я только хочу сохранить каждый 12-й файл, так файл 00012.end, файл 00024.end... файл 99996.end и удалить все остальное.

Файлы могут также иметь числа ранее в их имени файла и обычно имеют форму: file.00064.name.99999.end

Я использую оболочку Bash и не могу выяснить, как циклично выполниться по файлам и затем вынуть число и проверить, является ли это number%%12=0 удаление файла, если нет. Кто-либо может помочь мне?

Спасибо, Dorina

14
задан 13 September 2016 в 02:27

6 ответов

Можно использовать расширение скобки 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

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

6
ответ дан 23 November 2019 в 02:53

Немного долго, но то, что прибыло по моему мнению.

 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-й файл одиннадцать раз.

1
ответ дан 23 November 2019 в 02:53

Во всей скромности я думаю, что это решение намного более хорошо, чем другой ответ:

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 аргументами.

0
ответ дан 23 November 2019 в 02:53

Поскольку использование только колотит, мой первый подход был бы к: 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
0
ответ дан 23 November 2019 в 02:53

Вот решение для 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() который удаляет файлы (но не каталоги).

18
ответ дан 23 November 2019 в 02:53

Учитывая, что Ваши имена файлов находятся в формате 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 проверки, не равен ли остаток "" для обнуления.
  • Если остаток не равен 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

Будет работать правильно также.

12
ответ дан 23 November 2019 в 02:53

Другие вопросы по тегам:

Похожие вопросы: