Удалите все, кроме последнего n файла для каждой группы файлов, которые имеют один и тот же префикс в каталоге

Мой вопрос немного отличается от некоторых старых вопросов, просто прося «удалить все, кроме последних n файлов в каталоге».

У меня есть каталог, который содержит разные «группы» файлов, в которых каждая группа файлов имеет некоторый произвольный префикс, и каждая группа имеет хотя бы один файл. Я не знаю этих префиксов заранее, и я не знаю, сколько групп есть.

EDIT: на самом деле, я кое-что знаю о именах файлов, то есть все они следуют шаблону prefix-some_digits-some_digits.tar.bz2 . Здесь важна prefix часть, и мы можем предположить, что в каждом prefix нет цифры или тире.

Я хочу сделать следующее в сценарии bash :

  1. Пройдите через данный каталог, определите все существующие «группы» и для каждой группы файлов удалите все, кроме самых последних n файлов группы.

  2. Если для группы меньше n файлов, ничего не делайте для этой группы, то есть не удаляйте ни одного файла для этой группы.

Что такое надежный и безопасный способ сделать это в bash ? Не могли бы вы объяснить пошаговые инструкции?

4 Solutions collect form web for “Удалите все, кроме последнего n файла для каждой группы файлов, которые имеют один и тот же префикс в каталоге”

Сценарий:

 #!/bin/bash # Get Prefixes PREFIXES=$(ls | grep -Po '^(.*)(?!HT\d{4})-(.*)-(.*).tar.bz2$' | awk -F'-' '{print $1}' | uniq) if [ -z "$1" ]; then echo need a number of keep files. exit 1 else NUMKEEP=$1 fi for PREFIX in ${PREFIXES}; do ALL_FILES=$(ls -t ${PREFIX}*) if [ $(echo ${ALL_FILES} | wc -w) -lt $NUMKEEP ]; then echo Not enough files to be kept. Quit. continue fi KEEP=$(ls -t ${PREFIX}* | head -n${NUMKEEP}) for file in $ALL_FILES ; do if [[ "$KEEP" =~ "$file" ]]; then echo keeping $file else echo RM $file fi done done 

Объяснение:

  • Вычислить префиксы:
    • Ищите все файлы, следующие за регулярным выражением something-something-something.tar.bz2 , разрезая только первую часть до первой тире и сделайте ее уникальной.
    • результатом является нормализованный список PREFIXES
  • Итерации через все PREFIXES :
  • Вычислить ALL_FILES с помощью PREFIX
  • Проверьте, не превышает ли количество ALL_FILES количество ALL_FILES файлов -> если true, мы можем остановиться здесь, ничего не удалить
  • Вычислить файлы KEEP которые являются последними файлами NUMKEEP
  • ALL_FILES через ALL_FILES и проверьте, не ALL_FILES ли данный файл в списке файлов KEEP . Если это так: удалите его.

Пример результата при запуске:

 $ ./remove-old.sh 2 keeping bar-01-01.tar.bz2 keeping bar-01-02.tar.bz2 RM bar-01-03.tar.bz2 RM bar-01-04.tar.bz2 RM bar-01-05.tar.bz2 RM bar-01-06.tar.bz2 keeping foo-01-06.tar.bz2 keeping foo-01-05.tar.bz2 RM foo-01-04.tar.bz2 RM foo-01-03.tar.bz2 RM foo-01-02.tar.bz2 $ ./remove-old.sh 8 Not enough files to be kept. Quit. Not enough files to be kept. Quit. 

Я предполагаю, что файлы сгруппированы вместе с префиксом, когда они перечислены в лексическом порядке. Это означает, что нет групп с префиксом, который является суффиксом другой группы, например, нет foo-1-2-3.tar.bz2 который будет foo-1-2-3.tar.bz2 между foo-1-1.tar.bz2 и foo-1-2.tar.bz2 . В этом предположении мы можем перечислить все файлы, и когда мы обнаружим изменение префикса (или для самого первого файла), у нас есть новая группа.

 #!/bin/bash n=$1; shift # number of files to keep in each group shopt extglob previous_prefix=- for x in *-+([0-9])-+([0-9]).tar.bz2; do # Step 1: skip the file if its prefix has already been processed this_prefix=${x%-+([0-9])-+([0-9]).tar.bz2} if [[ "$this_prefix" == "$previous_prefix" ]]; then continue fi previous_prefix=$this_prefix # Step 2: process all the files with the current prefix keep_latest "$n" "$this_prefix"-+([0-9])-+([0-9]).tar.bz2 done . #!/bin/bash n=$1; shift # number of files to keep in each group shopt extglob previous_prefix=- for x in *-+([0-9])-+([0-9]).tar.bz2; do # Step 1: skip the file if its prefix has already been processed this_prefix=${x%-+([0-9])-+([0-9]).tar.bz2} if [[ "$this_prefix" == "$previous_prefix" ]]; then continue fi previous_prefix=$this_prefix # Step 2: process all the files with the current prefix keep_latest "$n" "$this_prefix"-+([0-9])-+([0-9]).tar.bz2 done 

Теперь мы решаем проблему определения самых старых файлов среди явного списка .

Предполагая, что имена файлов не содержат символов новой строки или символов, которые ls не отображаются буквально, это можно реализовать с помощью ls :

 keep_latest () ( n=$1; shift if [ "$#" -le "$n" ]; then return; fi unset IFS; set -f set -- $(ls -t) shift "$n" rm -- "$@" ) 

Я знаю, что это помечено bash но я думаю, что это будет проще с zsh :

 #!/usr/bin/env zsh N=$(($1 + 1)) # calculate Nth to last typeset -U prefixes # declare array with unique elements prefixes=(*.tar.bz2(:s,-,/,:h)) # save prefixes in the array for p in $prefixes # for each prefix do arr=(${p}*.tar.bz2) # save filenames starting with prefix in arr if [[ ${#arr} -gt $1 ]] # if number of elements is greather than $1 then print -rl -- ${p}*.tar.bz2(Om[1,-$N]) # print all filenames but the most recent N fi done 

сценарий принимает один аргумент: n (количество файлов)
(:s,-,/,:h) – модификаторы glob :s заменяет первое - с / и :h извлекает голову (часть до последней косой черты, которая в этом случае также является первой косой чертой, поскольку есть только один )
(Om[1,-$N]) являются glob-классификаторами, Om сортирует файлы, начиная с самого старого, и [1,-$N] выбирает от первого до N-го до последнего
Если вы довольны результатом, замените print -rl на rm чтобы фактически удалить файлы, например:

 #!/usr/bin/env zsh typeset -U prefixes prefixes=(*.tar.bz2(:s,-,/,:h)) for p in $prefixes arr=(${p}*.tar.bz2) && [[ ${#arr} -gt $1 ]] && rm -- ${p}*.tar.bz2(Om[1,-$(($1+1))]) 

В соответствии с запросом этот ответ имеет тенденцию к «надежной и безопасной», как вы просили, в отличие от быстрой и грязной.

Переносимость: этот ответ работает на любой системе, которая содержит sh , find , sed , sort , ls , grep , xargs и rm .

Сценарий никогда не должен задыхаться в большом каталоге. Не выполняется расширение имени файла оболочки (что может захлебывать, если слишком много файлов, но это огромное количество).

Этот ответ предполагает, что префикс не будет содержать тире ( - ).

Обратите внимание, что при разработке сценарий отображает только файлы, которые будут удалены. Вы можете заставить его удалить файлы, xargs -d '/n' rm вывод цикла while в xargs -d '/n' rm который закомментирован в скрипте. Таким образом, вы можете легко протестировать скрипт, прежде чем включить код удаления.

 #!/bin/sh -e NUM_TO_KEEP=$(( 0 + ${1:-64000} )) || exit 1 find . -maxdepth 1 -regex '[^-][^-]*-[0-9][0-9]*-[0-9][0-9]*.tar.bz2' | sed 's/-.*//; s,^\./,,' | sort -u | while read prefix do ls -t | grep "^$prefix-.*-.*\.tar\.bz2$" | sed "1,$NUM_TO_KEEP d" done # | xargs -d '\n' rm -- 

Параметр N (количество поддерживаемых файлов) по умолчанию составляет 64000 (т.е. все файлы хранятся).

Аннотированный код

Получите аргумент командной строки и проверьте на integer путем добавления, если не заданы значения по умолчанию по умолчанию для 64000 (фактически все):

 NUM_TO_KEEP=$(( 0 + ${1:-64000} )) || exit 1 

Найти все файлы в текущем каталоге, которые соответствуют формату файла:

 find . -maxdepth 1 -regex '[^-][^-]*-[0-9][0-9]*-[0-9][0-9]*.tar.bz2' | 

Получить префикс: удалить все после префикса и удалить «./» в начале:

 sed 's/-.*//; s,^\./,,' | 

Сортируйте префиксы и удаляйте дубликаты ( -u – unique):

 sort -u | 

Прочитайте каждый префикс и процесс:

 while read prefix do 

Перечислите все файлы в каталоге, отсортированные по времени, выберите файлы для текущего префикса и удалите все строки за пределами файлов, которые мы хотим сохранить:

  ls -t | grep "^$prefix-.*-.*\.tar\.bz2$" | sed "1,$NUM_TO_KEEP d" 

Для тестирования закомментируйте код для удаления файла. Использование xargs, чтобы избежать каких-либо проблем с длиной командной строки или пробелами в именах файлов, если таковые имеются. Если вы хотите, чтобы сценарий создавал журнал, добавьте -v в rm например: rm -v -- . Удалите # чтобы включить код удаления:

 done # | xargs -d '\n' rm -- 

Если это сработает для вас, пожалуйста, примите этот ответ и проголосуйте. Благодарю.

  • Как получить приемлемую последнюю измененную временную метку для моего сайта Jekyll с помощью сценария оболочки?
  • Calc добавляет '(апостроф) при импорте времени из CSV
  • разница во времени между двумя датами ISO8601 с дробной второй частью
  • Solaris + отметка времени файла отображения
  • По какому случаю будет изменен индекс inode?
  • Как использовать awk для чтения данных между всеми частыми временными интервалами
  • Кодирование видео с Unix Timestamps + ms
  • Полная дата файла (без утилит GNU)
  • Почему «touch -a» также устанавливает ctime?
  • Удалить файлы, более новые, чем X день и час
  • «Обновляет» zip-файл с опцией -FS изменяет время модификации файла?
  • Interesting Posts
    Linux и Unix - лучшая ОС в мире.