LINUX.ORG.RU
ФорумAdmin

Ротация файлов и папок

 , , , ,


1

1

Всех привествую. Есть такая задача. На сервере лежат папки подобного строения. В которыз лежат архивы бэкапов

/opt/backup/*/*/pg/2020/09/07

даты естественно меняются в зависимости от созданного бэкапа

Сливаются они по FTP с других хостов. Теперь условия, которые я не знаю как реализовать в скрипте.

/opt/backup/*/*/pg/2020/09/07

хочется чтоб проверялась папка с месяцем по маске

/opt/backup/*/*/pg/*/*/* 

и удаляла все старые папки, которые старше 14 дней, затем поднималась на папку выше, т.е. шла проверка месяца /opt/backup///pg// и так же проверяла изменения за последние 14 дней, в общем такое же действие и с годом.

Все это можно реализовать с помощью

find /opt/backup/*/*/pg/*/*/* -type d -mtime +336

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

Ответ на: комментарий от Morin

Хорошо, предположим я положу файлы в /opt/backup/pg Как можно прописать скрипт, для проверки условии. Например зашел в папку

backups=14

cd /opt/backup/pg
ls | wc -l

if [ $backups -gt 14 ]
then
   [удаляем самый старый файл в папке]
else
   echo 'exit 0'
fi

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

shift-47 ()
Ответ на: комментарий от shift-47
#!/bin/bash
backups=14
folder=/opt/backup/pg/
check=$(ls "$folder" | wc -l)

if [ $check -gt $backups ]
then
   ls $folder | head -n +1 | xargs rm -rf
else
   echo 'Лимит бэкапов не достигнут'
fi

Написал вот такой скрипт, получается, если копий больше 14, то мы удаляем самый первый файл в списке вызываемый ls.

Есть еще один вопрос, как можно запускать скрипт по вложеным папкам? Например. Использовав скрипт выше заходить в каждую подпапку например

/opt/backup/172.10.10/1/pg/
/opt/backup/172.10.10/2/pg/
/opt/backup/172.10.11/1/pg/
/opt/backup/172.10.10/2/pg/ 

Можете подсказать?

shift-47 ()
Ответ на: комментарий от shift-47
#!/bin/bash
backups=14
folder=/opt/backup/pg/
check=$(ls "$folder" | wc -l)

if [ $check -gt $backups ]
then
   cd $folder && ls | head -n +1 | xargs rm
   echo 'Del file'
else
   echo 'Лимит бэкапов недостигнут'
fi

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

/opt/backup/172.10.10/1/pg/
/opt/backup/172.10.10/2/pg/
/opt/backup/172.10.11/1/pg/
/opt/backup/172.10.11/2/pg/
shift-47 ()
Ответ на: комментарий от shift-47

Можете подсказать?

Можем. Составляете алгоритм и реализовываете и спрашиваете что именно в каком шаге не получается. Скажем такой:

1. Получаем список файлов. У нас же bash, ну накой тут ls?! ls сделан для людей, а не скриптов! FILES=(*)

2. Узнаем сколько элементов в списке. check=${#FILES[*]}

3. Создаем список, где каждый элемент есть дата файла из имен в предыдущем списке

4. Сортируем

5. Удалям

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

Не совсем понял, как лучше реализовать. Давайте опишу пока словами) У меня есть список папкок для каждого хоста указан.

/opt/backpup/172.10.1/1/pg/
/opt/backpup/172.10.1/2/pg/

И так далее для каждого хоста создана папка на сервере (или будет создана в момент подключения к севаку по FTP)

Задача такая, заходить в каждую папку т.е. тут нужено кажется какое-то регулярное выражение или подставить маску и засунуть все это дело в цикл. Что для меня уже тяжело в освоении)

/opt/backpup/*/*/pg/
/opt/backpup/*/*/pg/

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

cd $folder && ls | head -n +1 | xargs rm

Я понимаю, что это костыль, но не понимаю как сделать более-менее правильно.

shift-47 ()
Ответ на: комментарий от shift-47

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

Цикл for var in маска и сделает сразу два дела. Можно даже внутри проверять, что это каталог на каждом шаге.

пока не понял, как лучше заменить эту строчку

У вас всегда надо только один файл оставлять?

vodz ★★★★★ ()
Ответ на: комментарий от shift-47
#!/bin/bash
dir_to_bu="/opt/backup/pg"
cd $dir_to_bu
n_of_day=14
count=`ls | wc -l`
n=$((count-n_of_day)
if (($n > 0));
then
m=$((n+1))
find $dir_to_bu -mtime +$m -delete
fi

возможно где-то ошибки, протестировать можно создав файлы в определенный день

touch -t 09061200 a.test

формат месяц-день-часы-минуты

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

Morin ★★★ ()
Последнее исправление: Morin (всего исправлений: 2)

а вообще есть в этом что-то неправильное, очистку места должен запускать тот-же скрипт что и создает бэкап, тогда у тебя гарантированно есть одна рабочая копия.

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

Понял, нужно значит в сторону циклов смотреть.

У вас всегда надо только один файл оставлять?

Нет, в переменоой backups указал, кол-во оставляемых файлов в папке.

cd $folder && ls | head -n +1 | xargs rm

удаляет самый первый файл вызванный с помощью head -n

shift-47 ()
Ответ на: комментарий от Morin

Дело в том, что с точки зрения безопасности было решено заливать бэкапы со стороны клиента по FTP. На стороне клиента создаются, храняться бэкапы (последние 5-6 штук) и отправляются на FTP.

Остается не решенная задача как контроль места на сервере, имею ввиду папки куда заливаются бэкапы

shift-47 ()
Ответ на: комментарий от shift-47

А блин, head, выше было с tail...

Если получили список файлов в FILES=(*) и если вам не надо сортировать, то просто берете нужное количество элементов, первые это ${FILES[*]:0:N}, где N можно прямо там писать арифметическое выражение от общего числа.

vodz ★★★★★ ()
Последнее исправление: vodz (всего исправлений: 1)
Ответ на: комментарий от shift-47

я тут глянул на свой пример можно переделать, посмотри в сторону

ls -1rt | head -n $n

$n - вычисленное кол-во лишних файлов, по идее снимет ограничение на непрерывность дат при удалении, да удалять уже через rm

зы додумывать список или цикл уже оставлю тебе, мне уже лень, да и домой пора :)

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

Да нормально всё и так:

printf '%s\n' pg/*/*/* | head -n-11 | xargs -r rm

проверяем:

cd /tmp
mkdir shift-47
cd shift-47
mkdir -p pg/{2018..2020}/{01..12}/{01..30}
tree
<тут выведется простыня на 1080 дней>
printf '%s\n' pg/*/*/* | head -n-11 | xargs -r rm
tree
тут удалятся все дни, кроме последних 14

Если даты в формате 2020/1/1 вместо 2020/01/01 - работать в таком виде не будет.

legolegs ★★★★★ ()
Последнее исправление: legolegs (всего исправлений: 1)
Ответ на: комментарий от shift-47
for i in $(ls -1t "${backup_dir}"/pg-*.txz | tail -n+15); do
	if [ -f "${i}" ]; then
		rm "${i}"
	fi
done

Где ls -1t — показать листинг файлов по дате от новых до старых, tail -n+15 — показать хвост списка с пятнадцатой позиции (отсеять первые четырнадцать).

Далее проверка на существование файла (и что это файл, а не что-то другое), и если да, то его удаление.

я не силен в баше

Баш — зло. POSIX Shell — наше всё. ☺

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

держать в дире помойку из бекабов и шлака так себе затея

Держишь отдельную диру для каждого, например /backupdir/pg/pg-2020-09-06.txz, /backupdir/www/www-2020-09-06.txz и так далее.

Но когда у тебя все файлы определённой "категории" в одной дире, тебе не надо извращаться, просто делаешь листинг файлов в дире по времени, тайлаешь лишнее и удаляешь.

Я этим пользуюсь уже очень давно, и ни разу не подводило. А извращаться ради красивой иерархии бэкапа просто непрактично.

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

Да нормально всё и так

Если даты в формате 2020/1/1 вместо 2020/01/01 - работать в таком виде не будет.

А мой пример выше позволяет именовать файлы как угодно (дата используется только для непересечения имён), так как отсеивание лишнего происходит листингом ls по дате модификации. ☺

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

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

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

обрати внимание бэкапятся много инстансов

Для каждого инстанса своя дира. Например у меня иерархия примерно следующая:

backupdir/
  host/
    thing/
      thing-2020-09-04.txz
      thing-2020-09-05.txz
      thing-2020-09-06.txz

Мне что, как маленьким всем разжёвывать как всё должно работать? Делюсь базовыми принципами, а дальше у людей природой предусмотрен мозг, которым иногда нужно пользоваться.

никогда не знаешь до каких размеров это разрастётся

Анон, тред про ротацию, потому оно разрастётся до определённых размеров. Никаких 1000000000 файлов в дире, так как нормальные люди хранят бэкапы каждого инстанса в отдельной дире.

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

Баш — зло. POSIX Shell — наше всё. ☺

Даже в POSIX shell-ах есть встроенный массив $*, то есть передав маску в функцию получим массив элементов, который прогнав через stat или лучше через [ -nt ] получим даты файлов, а через пузырек и арифметическим концом от $# можно сразу во внешнем цикле удалять. На что надеяться не следует, так это на наличие в bash встроенного stat, это появилось только в 5 версии. А запрещение bash без отдельного требования к задаче - просто идиотизм, ибо в вашем ls проблем больше от пробелов в именах, и вообще в тормозах от внешних процессов, чем смысла. Еще раз - ls придуман для людей, а не скриптов!

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

Т.к. у меня папки создаются при создании ftp пользователя, нет смысла заходить в них и создавать. Прописал 1 строчку

#!/bin/bash
printf '%s\n' /opt/backup/*/*/pg/* | head -n-14 | xargs -r rm

Скормил к крон, на выполнение каждый час. Думаю мне этого будет достаточно, сегодня проведу тест в более-менее боевых условиях и отпишу по итогу, отрабатывает или нет) По сути своей должно работать. Большое спасибо за ответ

shift-47 ()
Ответ на: комментарий от mord0d

добавляем инстансов до отупения, разжёвывать надо тсу, это он один скрипт на обход всего лупит, подумать над квотами и читаемостью видимо еще в голову не пришло

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

Даже в POSIX shell-ах есть встроенный массив $*

$* передаёт аргументы как сырую строку, массив — это $@.

А запрещение bash без отдельного требования к задаче - просто идиотизм

 % file /bin/sh
/bin/sh: ELF 64-bit LSB executable, x86-64, version 1 (FreeBSD), dynamically linked, interpreter /libexec/ld-elf.so.1, for FreeBSD 12.1, FreeBSD-style, stripped
 % which -a bash
 % 

Чем не требование к задаче? ☺

ls придуман для людей, а не скриптов!

Ну да, ну да…

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

$* передаёт аргументы как сырую строку, массив — это $@.

Нет. $* - это аналогия function маска*. Разный смысл при передачи массива как аргументы - к делу не относится, это просто «каким значком сделать вот такое, а давайте введем еще один значек $@, который будет уникальным синтаксисом, но только в двойных кавычках». Не учите меня bash-у, постыдились бы, для вашего ls как раз пробелы в именах и ломают разницу между $* и «$@», ибо там её не пахло.

Чем не требование к задаче?

Вы в логику умеете? Требование к задаче рисует заказчик, а не реализатор. Поставил бы автор такое требование, сделали бы без башизмов. В прошлом комменте я всё и расписал как, правильно и оптимально.

Ну да, ну да…

Именно.

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

Не учите меня bash-у

То же могу сказать про POSIX Shell. ☺

для вашего ls как раз пробелы в именах и ломают разницу между $* и «$@»

IFS='
'

правильно и оптимально

Нет. ☺

Вы в логику умеете?

В зависимости от оппонента. ^_~

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

Как итог, оказалось, что эта конструкция не работает, т.к. удаляет все файлы например в /opt/backup/x.x.x/2/pg/, а в /opt/backup/x.x.x/2/pg/ остаются бэкапы больше 14 штук. Привел ее к подобному виду:

echo "printf '%s\n' /opt/backup/$1/$2/pg/* | head -n-14 | xargs -r rm" >> /root/delfiles.sh

Через ENV переменные выше в коде создается виртуальный пользователь FTP (скрыто). Получается все, что скармливается в echo "" добавляется в конец исполняемого файла. Данный файл запускается каждый час. Костыль конечно вы скажете, но ничего умнее пока не придумал.

shift-47 ()
Ответ на: комментарий от mord0d

То же могу сказать про POSIX Shell. ☺

В смысле? 35 лет назад, когда я начинал системно программировать на bsd, там posix shell был так убог, что все юзали c-shell, а bash-ем как и линуксом ещё не пахло.

IFS=$'\n'

Пробелы включают в себя и \n. Прикол: в ls null-terminated так и не завезли, но там давно есть quote для имён. Вот только dequote появился в bash с версии 5 ${var@E}. Вот так и живите теперь в раскоряку.

В зависимости от оппонента

Отож. Раз иногда, значить в целом — нет.

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

Как итог, оказалось, что эта конструкция не работает, т.к. удаляет все файлы например в /opt/backup/x.x.x/2/pg/, а в /opt/backup/x.x.x/2/pg/ остаются бэкапы больше 14 штук.

Если у вас в $1 $2 тоже маски, то оно так и будет — получать последние файлы из общего полученного списка после раскрытия масок. Вам надо тогда обходить каждый подкаталог отдельно в цикле и там уже брать последние файлы.

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

IFS=$‘\n’

Нет, именно

IFS='
'

Сишных $'' нет в POSIX, и '\n' это literal \ + n.

Прикол: в ls null-terminated так и не завезли

В printf во FreeBSD \0 тоже не работает.

Вот только dequote появился в bash с версии 5 ${var@E}.

Да пофиг мне на этот Bash, нет его. ☺

Раз иногда, значить в целом — нет.

«Этта ЛОР, детка!» © @Chelobaka

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

Нет, именно

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

В printf во FreeBSD \0 тоже не работает.

Лень проверять, но даже если б работало, то в отсутствии у read -d «» в posix вам бы всё равно не помогло. Да, кстати, в posix нет xargs!

Этта ЛОР, детка!

Ну дык, не хочешь думать о людях плохое, а опять натыкаешься на детский сад.

vodz ★★★★★ ()
Последнее исправление: vodz (всего исправлений: 1)
Ответ на: комментарий от shift-47

echo "...." >> /root/delfiles.sh

Зачем? Можно тогда уж $1/$2 оставить в самом delfiles.sh и вызывать /root/delfiles.sh host something.

И что, файл бесконечно растёт?

Почему нельзя так:

for host in host1 host2 host3; do
  for thing in thing1 thing2; do
    printf '%s\n' /opt/backup/$host/$thing/pg/* | head -n-14 | xargs -r rm
  done
done

Если список хостов меняется, то его можно брать из файла хоть простым рабоче-крестьянским for host in $(cat hosts.list); do.

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

Для протокола: в данном конкретном случае я не против использования ls --sort=time dir/ вместо printf "%s/n" dir/*. Удивительных имён файлов в такой задаче не ожидается, но если даты будут не по ISO 8601, то сортировку так сделать проще.

Но для пользовательских файлов, конечно, справедливо правило do not parse ls.

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

Это выглядит как обрывок JSON. Эстетически не нравится.

Ну если это, то оно ведь на самом деле будет выглядеть лучше, так как всё равно надо присвоить количество минус 14 в переменную, проверить на больше нуля, так что в самом цикле будет нагляднее "${FILES[*]:0:n14}"

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

Да, так лучше. И при обходе массива башем можно заодно и пустые директории подчищать, если бекапы всё-таки ракидываются в ГГГГ/ММ/ДД и другие действия делать. С xargs всё-таки неудобно дёргать больше одной команды.

legolegs ★★★★★ ()