LINUX.ORG.RU

Удалять архивы старше 14 дней (по названию файла), но не последние 2. bash

 


1

3

На сервере есть директория с бэкапами баз данных разных проектов, 1 проект - 1 директория, внутри архивы с названием «$app-$timestamp».

Нужен скрипт, который будет удалять архивы старше 14 дней, но не последние 2. Системное время на файлах не правильное, надо из имён выковыривать. $app надо учитывать, чтобы предположим: последние 2 версии app1 не удалялись.

-backups
	_project1
		_app1-100619.tar.gz
		_app1-200619.tar.gz
		_app1-260619.tar.gz
		_app1-300619.tar.gz
		_app2-010519.tar.gz
		_app2-040519.tar.gz
		_app2-100619.tar.gz
		_app2-160619.tar.gz
		_app2-260619.tar.gz
		_app3-010519.tar.gz
		_app3-100519.tar.gz
		_app3-210619.tar.gz
		_app3-300619.tar.gz
	_project2
		_app1-100619.tar.gz
		_app1-200619.tar.gz
		_app1-260619.tar.gz
		_app1-300619.tar.gz
		_app2-010519.tar.gz
		_app2-040519.tar.gz
		_app2-100619.tar.gz
		_app2-160619.tar.gz
		_app2-260619.tar.gz
		_app3-010519.tar.gz
		_app3-100519.tar.gz
		_app3-210619.tar.gz
		_app3-300619.tar.gz
	_project3
		...


Последнее исправление: Krop_2 (всего исправлений: 6)

Кто-ж так файлы именует...

А по теме, если как сказали find не удовлетворяет, можно ls -t |head -n2 >except.tmp и отфильтровать потом эти файлы.

sr11
()
Ответ на: комментарий от sr11

Можно в файл не слать, а чекать прямо в цикле, скармливая в цикл файлы бэкапов по одному. У меня так работает уже не первый год (может немного костыльно, зато надёжно).

mord0d ★★★★★
()

Возьми и напиши скрипт на Python, декомпозируй задачу. Там это делается просто и поддерживаемо, без всяких какерских однострочников над которыми ты потом будешь ломать голову спустя некоторое время.

EXL ★★★★★
()
Ответ на: комментарий от mord0d

перл тоже понятен, ну как понятен - зависит от упоротости писателя.

deep-purple ★★★★★
()
Ответ на: комментарий от EXL

+

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

deep-purple ★★★★★
()
Ответ на: комментарий от sr11

А по теме, если как сказали find не удовлетворяет

Вообще он нужен. Результат поиска загнать в массив, дальше ls | head, результат из массива убрать, если он там есть, грохнуть оставшееся в массиве.

AS ★★★★★
()
Ответ на: комментарий от Dimarius

find .../backup -ctime +14 -delete

Если бакапов не делалось более 14 дней, то ничего не останется. А ТС просил 2 оставить в любом случае.

AS ★★★★★
()

+1 за декомпозицию.
язык - любой, которым умеешь. в баше просто удобно манипулировать с файлами, переходить по каталогам.
цикл по проектам:
в проекте получаешь список файлов подходящих под условие архивов которые надо ротейтить.
если в баше, то с помощью cut,awk может rev, basename получаешь
подстроку 100619 с датой, из имени файла.
формируешь список на удаление: дата в алфавитносотировочном формате, имя файла.
удаляешь из списка 2 последних
также удаляешь на основе даты - младше 14 дней.
оставшиеся в списке файлы удаляешь(или переносишь в архив).

bl ★★★
()

Использовать нормальное средство резервного копирования, в котором это всё уже сделано как надо.

anonymous
()
Ответ на: комментарий от WitcherGeralt

Формой бумеранга — она возвращается к бросающему.

deep-purple ★★★★★
()
Ответ на: комментарий от AS

Тада примерно так: Добавить выборку по кол-ву файлов в диапазоне 0-14 дней, если 0, то

find .../backup -type f -ctime +14 -printf '%TY-%Tm-%Td %TT %p\n' | sort -r  | awk '{ print $3 }' | grep -A2 | rm -f
если 1, то:
find .../backup -type f -ctime +14 -printf '%TY-%Tm-%Td %TT %p\n' | sort -r  | awk '{ print $3 }' | grep -A1 | rm -f
если 2, то:
find .../backup -ctime +14 -delete

Dimarius
()
Ответ на: комментарий от a1batross

В bash не силен, сначала ковырял find с регулярными выражениями (find $path -iname «*[0-1][0-6][0-9][0-9][0-9][0-9].tar.gz») потом с -ctime, потом все стрер =), по тз bash или Ruby, но с Ruby ни разу не сталкивался.

Krop_2
() автор топика
Ответ на: комментарий от Dimarius

Тада примерно так: Добавить выборку по кол-ву файлов в диапазоне 0-14 дней, если 0, то

Заморочено что-то. Сделать два списка: свыше 14 и два последних, слить, отсортировать с нумерацией, отгрепать те, что с 1 и грохнуть.

AS ★★★★★
()

Покажи, какие в твоём примере файлы должны быть удалены. $app надо как-то учитывать?

Системное время на файлах правильное или надо из имён выковыривать? Имена дурацкие, да, iso-8601 рулит.

legolegs ★★★★★
()

Сформируй vector, содержащий имена удаляемых архивов, а затем удаляй все, кроме последних двух.

Владимир

anonymous
()
Ответ на: комментарий от anonymous

Все же более правильно - «Сформируйте vector, ...».

anonymous
()
Ответ на: комментарий от legolegs

Системное время на файлах не правильное, надо из имён выковыривать. $app надо учитывать, чтобы предположим: последние 2 версии app1 не удалялись.

Krop_2
() автор топика
Ответ на: комментарий от Krop_2

Время можно поправить таким скриптом:

awk '
BEGINFILE {
  if (match(FILENAME, /-(..)(..)(..)\.tar\.gz$/, d)) {
    print "file <"FILENAME"> -> YY MM DD: ",d[3],d[2],d[1];
    system("touch \""FILENAME"\" -t \""d[3]d[2]d[1]"1200\"");
  } else 
    print "wrong file <"FILENAME">.";
}' *-*.tar.gz

после этого find -type f -mtime +14 (не -ctime!) начинает работать.

legolegs ★★★★★
()
Последнее исправление: legolegs (всего исправлений: 1)
Ответ на: комментарий от Krop_2

Если исправлять время нельзя, то такие кривые даты можно сортировать так:

for i in _app1 _app2 _app3 _app4; do echo == $i ==; ls $i-* |  sort -t- -k 2.5 -k 2.3 -k 2.1 ;done
== _app1 ==
_app1-100619.tar.gz
_app1-200619.tar.gz
_app1-260619.tar.gz
_app1-300619.tar.gz
== _app2 ==
_app2-010519.tar.gz
_app2-040519.tar.gz
_app2-100619.tar.gz
_app2-160619.tar.gz
_app2-260619.tar.gz
== _app3 ==
_app3-010519.tar.gz
_app3-100519.tar.gz
_app3-210619.tar.gz
_app3-300619.tar.gz
_app3-010120.tar.gz
_app3-300620.tar.gz
== _app4 ==
_app4-010919.tar.gz
_app4-150919.tar.gz
_app4-300919.tar.gz
_app4-011019.tar.gz
_app4-151019.tar.gz
_app4-301019.tar.gz

если добавить | head -n -2| xargs rm то будет почти то, что тебе надо (останется ровно два бекапа каждого _app). Чтобы сохранить все за 15 последних дней придётся парсить имена, преобразовывать в дату и принимать решение, это можно сделать на том-же awk, но мне пока лень.

legolegs ★★★★★
()

Зачем изобретать велосипед, если есть готовые решения, в которых решены все типичные проблемы? Что-нибудь поменяется, и твой скрипт развалится.

anonymous
()
Ответ на: комментарий от legolegs

это можно сделать на том-же awk

Ну ладно бы сортировали awk-ом...

r='(.*)-(..)(..)(..)\.tar\.gz$'

if [[ $FILENAME =~ $r ]]; then
  echo "${BASH_REMATCH[1]}-$((BASH_REMATCH[4]+2000))${BASH_REMATCH[3]}${BASH_REMATCH[2]}.tar.gz"
fi

vodz ★★★★★
()
Ответ на: комментарий от legolegs

Считать 14 дней от 1 марта как будете?

s=$(date -d $((BASH_REMATCH[4]+2000))${BASH_REMATCH[3]}${BASH_REMATCH[2]} +%s) далее понятно вычитаем от текущей даты 14*24*60*60

vodz ★★★★★
()
Последнее исправление: vodz (всего исправлений: 2)
Ответ на: комментарий от legolegs

Ну вот на awk то же самое выглядит изящнее.

Оно выглядит примерно так же, ведь надо это всё засунуть в sort, а потом уже работать со считанным результатом с правильной датой в именах и не будет в date такого длинного BASH_REMATCH*

vodz ★★★★★
()

Мой лисапед:

#!/usr/bin/env python3

import re
from datetime import datetime

BACKUP_DAYS = 14
KEEP_N_FILES = 2

files = [
    '_app1-100619.tar.gz',
    '_app1-200619.tar.gz',
    '_app1-260619.tar.gz',
    '_app1-300619.tar.gz',
    '_app2-010519.tar.gz',
    '_app2-040519.tar.gz',
    '_app2-100619.tar.gz',
    '_app2-160619.tar.gz',
    '_app2-260619.tar.gz',
    '_app3-010519.tar.gz',
    '_app3-100519.tar.gz',
    '_app3-210619.tar.gz',
    '_app3-300619.tar.gz',
]

# collect file-date pairs
pairs = []
for file in files:
    s = re.sub(r'(^.*-|\.tar\.gz$)', '', file)
    pairs.append([file, datetime.strptime(s, '%d%m%y')])

# sort by date
pairs.sort(key = lambda x: x[1])

# collect files older than BACKUP_DAYS
rm_files = []
for file, date in pairs:
    if (datetime.today() - date).days > BACKUP_DAYS:
        rm_files.append(file)

# remove files except the last KEEP_N_FILES
for file in rm_files[:-KEEP_N_FILES]:
    print('rm', file)

RazrFalcon ★★★★★
()

Поставь правильное системное время и не парься.

PunkoIvan ★★★★
()

Если брать дату из названия, то можно примерно так:

#!/snap/bin/pwsh

$files = @(
'_app1-100619.tar.gz',
'_app1-200619.tar.gz',
'_app1-260619.tar.gz',
'_app1-300619.tar.gz',
'_app2-010519.tar.gz',
'_app2-040519.tar.gz',
'_app2-100619.tar.gz',
'_app2-160619.tar.gz',
'_app2-260619.tar.gz',
'_app3-010519.tar.gz',
'_app3-100519.tar.gz',
'_app3-210619.tar.gz',
'_app3-300619.tar.gz'
)

$res = $files | foreach {
    $date = [datetime]::parseexact(($_ -split '-|\.')[1],'ddMMyy',$null)
    $days = (new-timespan $date).days
    if ($days -ge 14) {
	[pscustomobject] @{ name = $_; days = $days }
    }
} | sort-object days

2..($res.count-1) | foreach { "rm $($res[$_].name)" }

anonymous
()

Всем спасибо. После многочисленного использования for, grep, и head --lines=-2, в итоге получился список на удаление all_apps_delete. Теперь нужно по списку удалить файлы старше 14 дней. Вопрос боян, так что не бомбить). Как это лучше сделать?

all_apps_delete
/media/sf_krop_linux/backups/_project1/_app1-100619.tar.gz
/media/sf_krop_linux/backups/_project1/_app1-200619.tar.gz
/media/sf_krop_linux/backups/_project2/_app1-100619.tar.gz
/media/sf_krop_linux/backups/_project2/_app1-200619.tar.gz
/media/sf_krop_linux/backups/_project3/_app1-100619.tar.gz
/media/sf_krop_linux/backups/_project3/_app1-200619.tar.gz
/media/sf_krop_linux/backups/_project1/_app2-010519.tar.gz
/media/sf_krop_linux/backups/_project1/_app2-040519.tar.gz
/media/sf_krop_linux/backups/_project1/_app2-100619.tar.gz
/media/sf_krop_linux/backups/_project2/_app2-010519.tar.gz
/media/sf_krop_linux/backups/_project2/_app2-040519.tar.gz
/media/sf_krop_linux/backups/_project2/_app2-100619.tar.gz
/media/sf_krop_linux/backups/_project3/_app2-010519.tar.gz
/media/sf_krop_linux/backups/_project3/_app2-040519.tar.gz
/media/sf_krop_linux/backups/_project3/_app2-100619.tar.gz
/media/sf_krop_linux/backups/_project1/_app3-010519.tar.gz
/media/sf_krop_linux/backups/_project1/_app3-100519.tar.gz
/media/sf_krop_linux/backups/_project2/_app3-010519.tar.gz
/media/sf_krop_linux/backups/_project2/_app3-100519.tar.gz
/media/sf_krop_linux/backups/_project3/_app3-010519.tar.gz
/media/sf_krop_linux/backups/_project3/_app3-100519.tar.gz

Krop_2
() автор топика
Ответ на: комментарий от legolegs

Но это наш с вами старый эстетический спор.

Ну вот как-то так. Всего один eval.

#!/usr/bin/env bash

PREF=/media/sf_krop_linux/backups/_project
FPREF=_app
SEP=-
SUFE=.tar.gz
# формат файлов:
#       prj        appN       day     mon      year
# $PREF[1-9]/$FPREF[1-9]$SEP[01..28][01..12][01..current_year]$SUFE
declare -a input

# PD временной период устаревания файлов (начальная установка: в днях)
declare -i PD=140

# минимальное количество старых файлов, которые нужно оставить каждой версии
declare -i MK=2


# рабочие переменные
declare -i p a i j t n
declare -ai arr_d f_idx


# Генератор тестовых случайных входных файлов с количеством IN
declare -i IN=4000

r02() { printf -v $2 "%02d" $((RANDOM%$1+1)); }

printf -v cy '%(%y)T' -1
for((i=0;i<IN;i++)); do
  r02 28  d
  r02 12  m
  r02 $cy y
  input[i]=$PREF$((RANDOM%9+1))/$FPREF$((RANDOM%9+1))$SEP$d$m$y$SUFE
done


# извлечение prj и appN, преобразование формата даты DDMMYY в 20YYMMDD
j=1+${#SEP}
for((i=0;i<IN;i++)); do
	file=${input[i]#"${PREF}"}
	# prj
	p=${file:0:1}
	file=${file#?/"${FPREF}"}
	# создаем массив с именем a_$prj_$appN и добавляем значение индекса
	eval a_${p}_${file:0:1}+=\($i\)
	# 20YYMMDD
	arr_d[i]=20${file:j+4:2}*10000+\(1${file:j+2:2}-101\)*100+1${file:j:2}
done

# преобразование формата даты 20YYMMDD в разницу секунд от устаревания
printf -v t '%(%s)T' -1
PD=t-PD*24*60*60
while read t; do
	arr_d[n++]=PD-t
done < <(for((i=0;i<IN;i++)); do echo ${arr_d[i]}; done | date -f - +%s)

# цикл по всем a_$prj_$appN
for v in ${!a_@}; do
  v+=[@]
  f_idx=(${!v})
  n=${#f_idx[@]}

  # сортировка и действие
  for((i=0;i<n;i++)); do
	  t=f_idx[i]
	  for((j=i+1;j<n;j++)); do
		  if [[ arr_d[t] -le arr_d[f_idx[j]] ]]; then
			  t=f_idx[j]
			  f_idx[j]=f_idx[i]
			  f_idx[i]=t
		  fi
	  done

	  if [[ i -lt n-MK && arr_d[t] -ge 0 ]]; then
		echo "rm ${input[t]}"
	  else
		echo "keep ${input[t]}"
	  fi
  done
  echo

done

vodz ★★★★★
()

Просить решение такой пустяковой задачки на форуме? Как-то мне даже противно стало. Тебе сложно прочитать man sh?

anonymous
()
Ответ на: комментарий от vodz

Только не говори мне, что ты реально так пишешь скрипты на баше. Это жесть, ужасный стиль, ты извращенец, мазохист.

anonymous
()
Ответ на: комментарий от anonymous

Только не говори мне, что ты реально так пишешь скрипты на баше.

Да, я пишу в том числе на shell-ах лет так 33.

Это жесть,

Ничем не могу помочь.

ужасный стиль,

Голословно. Кое в чём, конечно, и самому не нравится, например, отсутствие пробелов в арифметическом синтаксисе, но почему-то это именно общепринятый стиль, наверное так экономят такты на пробелах.

vodz ★★★★★
()
Ответ на: комментарий от anonymous

Ты так глаголишь, как будто на Bash’е возможно писать не ужасно.

EXL ★★★★★
()

Сначала переименовать оптом все файлы старше 14 дней в *.tar.gz.old

Если в каких-то директориях *.tar.gz осталось меньше двух - переименовать недостающие последние по дате *.tar.gz.old обратно

Удалить оптом все *.tar.gz.old

Переименование в пределах одного раздела не требует копирования, так что никакого особого оверхеда не будет.

Stanson ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.