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

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

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

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

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

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

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

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

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

решение1

Сценарий:

#!/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файлов, которые нужно сохранить -> если да, то можно остановиться, удалять нечего
  • Вычислите KEEPсамые последние файлы NUMKEEP.
  • Пройдитесь 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.

решение2

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

Переносимость: этот ответ работает в любой системе, которая содержит sh, find, sed, sort, ls, grep, xargsи 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 (т.е. сохраняются все файлы).

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

Получите аргумент командной строки и проверьте наличие целого числа путем сложения; если параметр не указан, то по умолчанию используется значение 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--уникальные):

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 --

Если это вам подходит, пожалуйста, примите этот ответ и проголосуйте за него. Спасибо.

решение3

Я предположу, что файлы сгруппированы по префиксу, когда перечислены в лексическом порядке. Это означает, что нет групп с префиксом, который является суффиксом другой группы, например, no, 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

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

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

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

решение4

Я знаю, что это отмечено тегом 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

скрипт принимает один аргумент:н(количество файлов)
(:s,-,/,:h)— это глобальные модификаторы, :sзаменяют первый -на /и :hизвлекают заголовок (часть до последнего слеша, которая в данном случае также является первым слешем, поскольку он всего один)
(Om[1,-$N])— это глобальные квалификаторы, 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))])

Связанный контент