Рейтинг:18

Ubuntu автоматически удаляет самый старый файл в каталоге, когда емкость диска превышает 90%, повторять до тех пор, пока емкость не станет ниже 80%

флаг bv

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

По сути, у меня есть ip-камеры, которые записывают на /home/бен/ftp/наблюдение/ но мне нужно убедиться, что на диске всегда достаточно места для этого.

Кто-нибудь, пожалуйста, помогите мне, как я могу настроить задание cron для:

Проверить, если /dev/sbd/ достиг 90% мощности. Если это так, удалите самый старый файл (и файлы в подпапках) /home/бен/ftp/наблюдение/ И повторяйте это до тех пор, пока /dev/sbd/ емкость ниже 80% Повторяйте каждые 10 минут.

J... avatar
флаг in
Что делать, если `/dev/sdb` (примечание: `sdb` != `sbd` — следите за опечатками в ваших сценариях!) когда-либо будет заполнен >80% независимо от содержимого `/home/ben/ftp/surveillance/` ? Это всегда удалит все ваши записи, оставив вас ни с чем. Лучше иметь камеры видеонаблюдения, записывающие на выделенный том, который не используется (т. е.) вашей операционной системой или любыми другими пользователями. В идеале камеры должны управлять этим сами, зная, какие файлы принадлежат им, и перезаписывая свои самые старые файлы, когда выделенное пространство на диске заполняется.
флаг bv
Хорошая мысль Дж... Мой каталог ftp на самом деле является внешним диском, установленным в моем домашнем каталоге, и теперь он предназначен только для записей с камер. Я не удосужился изменить структуру папок до того, как стал выделенным диском. Камеры также загружают стоп-кадр каждой записи в формате .jpg, поэтому я настрою еще один cron для периодического удаления всех .jpg.
Рейтинг:33
флаг in

Написание подобных сценариев для людей всегда заставляет меня нервничать, потому что, если что-то пойдет не так, произойдет одно из трех:

  1. Я корю себя за то, что, вероятно, опечатка уровня n00b
  2. Мне будут угрожать смертью, потому что кто-то слепо скопировал/вставил без:
    • прилагая усилия, чтобы понять сценарий
    • тестирование скрипта
    • наличие разумной резервной копии
  3. Все вышеперечисленное

Итак, чтобы снизить риск всех трех, вот вам стартовый набор:

#!/бин/ш
DIR=/home/ben/ftp/наблюдение
ДЕЙСТВИЕ=90
df -k $DIR | grep -vE '^Файловая система' | awk '{ print $5 " " $1 }' | при чтении вывода;
делать
  эхо $ вывод
  usep=$(echo $output | awk '{ print $1}' | cut -d'%' -f1 )
  partition=$(echo $output | awk '{print $2}')
  если [ $usep -ge $ACT ]; тогда
    echo "Недостаточно места \"$partition ($usep%)\" на $(имя хоста) по состоянию на $(дата)"
    oldfile=$(ls -dltr $DIR/*.gz|awk '{ print $9 }' | head -1)
    echo "Давайте удалим \"$oldfile\" ..."
  фи
сделано

ВНИМАНИЕ:

  1. Этот скрипт ничего не удаляет

  2. ДИР это каталог для работы с

  3. ДЕЙСТВОВАТЬ это минимальный процент, необходимый для действия

  4. Только один файл — самый старый — выбран для «удаления».

  5. Вы захотите заменить *.gz с фактическим типом файла ваших видео наблюдения.
    НЕ ИСПОЛЬЗОВАТЬ *.* ИЛИ ЖЕ * САМ!

  6. Если раздел, содержащий ДИР имеет мощность больше, чем ДЕЙСТВОВАТЬ, вы увидите такое сообщение:

    97% /dev/sda2
    Недостаточно места «/dev/sda2 (97%)» на ubuntu-vm по состоянию на среду, 12 января, 07:52:20 UTC 2022.
    Давайте удалим "/home/ben/ftp/surveillance/1999-12-31-video.gz"...
    

    Опять этот скрипт не будет удалить что-либо.

  7. Если вы удовлетворены результатом, вы можете продолжить изменять скрипт, чтобы удалить/переместить/архивировать по своему усмотрению.

Часто тестируйте. Хорошо протестируйте. И помните: ставя г.м. в сценарии нет отмены.

флаг ao
Глупо передавать `grep` в `awk`, вы можете просто добавить `!/^Filesystem/` в начало команды awk. Или вы можете указать `df` производить только тот вывод, который вам нужен, и использовать `sed` для удаления заголовка и знака процента: `usep=$(df --output=pcent $DIR | sed '1d;s/%/ /')`
Peter Cordes avatar
флаг fr
`echo $output` должен заключаться в кавычки `"$output"`, так как вам не нужно специально разбивать его на слова. Вероятно, это нормально, но даже в обычных системах некоторые съемные носители в конечном итоге монтируются по путям с пробелами в их имени, что на практике может привести к сообщениям об ошибках. Итак, эмпирическое правило: всегда цитируйте расширения переменных, если вы специально не хотите разбивать слова или какой-либо другой эффект, который блокирует цитирование.
флаг ua
Аналогично, но с использованием функции `firstaction` logrotate: https://serverfault.com/questions/372809/free-space-driven-log-rotation-on-linux
Eric Duminil avatar
флаг us
@NickMatteo Если соответствующий код работает нормально и читаем, глупо утверждать, что это глупо, потому что он может быть написан по-другому на 45-летнем и не очень читаемом языке. Каналы UNIX великолепны и удобочитаемы, кого волнует, можно ли написать команду с одним или двумя каналами меньше?
флаг ao
@EricDuminil: несколько распространенное использование `grep 'PATTERN' | awk '{делать что-то для совпадающих строк}'` объективно глупо, поскольку вся цель awk состоит в том, чтобы делать что-то для совпадающих строк, и вы могли бы просто написать `awk '/PATTERN/ {делать что-то}'`.
Eric Duminil avatar
флаг us
@NickMatteo: вы можете использовать любые инструменты, с которыми вы знакомы. matigo, кажется, доволен `grep ... | awk ...`, вас устроит `awk '/PATTERN/ {делать что-то}`, а я буду доволен коротким скриптом на Ruby или Python. Пока сценарии работают и читаются, никто не ошибается, и никакая команда не является глупой и уж точно не глупой объективно.
флаг cn
@EricDuminil Они могут не быть объективно глупыми, но есть аргумент, что сайт вопросов и ответов должен учить людей тому, как работают команды. Передача `awk` создает впечатление, что команда не может справиться с этим сама.Также полезно не использовать несколько команд через конвейер и замедлять процесс. Короче говоря, есть критерии, по которым можно утверждать, что использование awk по назначению является лучшим ответом.
флаг bv
@matgio - я не смог заставить `oldfile=$(ls -dltr $DIR/*.mp4|awk '{ print $9 }' | head -1)` работать, но я заставил это работать `oldfile=$( find $DIR -name "*.mp4" -type f | sort | head -n 1)` Если есть какая-то причина, по которой мой метод не следует использовать?
флаг in
@Beno â Вы получаете сообщение об ошибке с первой командой? В любом случае, если `find` даст вам то, что вам нужно, не стесняйтесь использовать его. Сценарий — это просто «стартовый набор», который поможет вам разобраться с некоторыми предварительными вещами.
Рейтинг:12
флаг kz

Я бы использовал Python для такой задачи. Это может привести к большему количеству кода, чем чистое решение bash, но:

  • это (IMO) проще проверить, просто используйте питест или же объединяться модуль
  • это читается для людей, не использующих Linux (ну, за исключением get_device функция, специфичная для Linux...)
  • легче начать (опять же IMO)
  • Что делать, если вы хотите отправить несколько писем? Чтобы вызвать новые действия? Скрипты можно легко дополнить с помощью такого языка программирования, как Python.

Начиная с Python 3.3, шутил модуль поставляется с функцией с именем disk_usage. Его можно использовать для получения информации об использовании диска на основе заданного каталога.

Незначительная проблема заключается в том, что я не знаю, как легко получить имя диска, т.е. /dev/sdb, даже несмотря на то, что можно получить сведения об использовании его диска (используя любой каталог, смонтированный на /dev/sdb, в моем случае $ГЛАВНАЯ Например). Я написал функцию под названием get_device для этой цели.

#!/usr/bin/env python3
импортировать аргументы
из os.path импортировать getmtime
из Shutil импортировать disk_usage, rmtree
из системного импорта выход
из пути импорта pathlib
от ввода импорта Iterator, Tuple


def get_device (путь: путь) -> ул:
    """Найти монтирование для данного каталога. Это необходимо только для ведения журнала."""
    # Прочтите /etc/mtab, чтобы узнать о точках монтирования
    mtab_entries = Путь("/etc/mtab").read_text().splitlines()
    # Создаем список точек монтирования и устройств
    mount_points = dict([list(reversed(line.split(" ")[:2])) для строки в mtab_entries])
    # Находим точку монтирования заданного пути
    в то время как path.resolve(True).as_posix() не находится в mount_points:
        путь = путь.родитель
    # Возвращаем устройство, связанное с точкой монтирования
    вернуть mount_points[path.as_posix()]


def get_directory_and_device(path: str) -> Tuple[str, Path]:
    """Выйти из процесса, если каталог не существует."""
    fs_path = Путь (путь)
    # Путь должен существовать
    если не fs_path.exists():
        print(f"ОШИБКА: Нет такого каталога: {путь}")
        выход(1)
    # И путь должен быть допустимым каталогом
    если не fs_path.is_dir():
        print(f"Путь должен быть каталогом, а не файлом: {путь}")
        выход(1)
    # Получить устройство
    устройство = get_device (fs_path)

    возвращаемое устройство, fs_path


def get_disk_usage(path: Path) -> float:
    # Shutil.disk_usage поддерживает путь как объекты, поэтому нет необходимости приводить к строке
    использование = использование_диска (путь)
    # Получить использование диска в процентах
    вернуть использование.использовано/использование.всего * 100


def remove_file_or_directory(path: Path) -> None:
    """Удалить указанный путь, который может быть каталогом или файлом."""
    # Удалить файлы
    если path.is_file():
        путь.отключить()
    # Рекурсивно удалить деревья каталогов
    если path.is_dir():
        rmtree (путь)


определение find_oldest_files(
    путь: путь, шаблон: str = "*", порог: int = 80
) -> Итератор[Путь]:
    """Перебрать файлы или каталоги, присутствующие в каталоге, которые соответствуют заданному шаблону."""
    # Перечислить файлы в каталоге, полученном в качестве аргумента, и отсортировать их по возрасту
    файлы = отсортированные (path.glob (шаблон), key = getmtime)
    # Выдавать пути к файлам до тех пор, пока использование не станет ниже порогового значения
    для файла в файлах:
        использование = get_disk_usage (путь)
        если использование < порог:
            сломать
        выходной файл


защита check_and_clean(
    путь: ул,
    порог: интервал = 80,
    удалить: логическое значение = ложь,
) -> Нет:
    """Основная функция"""
    устройство, fspath = get_directory_and_device(путь)
    # Shutil.disk_usage поддерживает путь как объекты, поэтому нет необходимости приводить к строке
    использование = использование_диска (путь)
    # Примите меры, если это необходимо
    если использование > порог:
        Распечатать(
            f"Использование диска превышает пороговое значение: {usage:.2f}% > {threshold}% ({device})"
        )
    # Перебираем файлы для удаления
    для файла в find_oldest_files(fspath, "*", порог):
        print(f"Удаление файла {файл}")
        если удалить:
            remove_file_or_directory(файл)


def main() -> Нет:

    синтаксический анализатор = argparse.ArgumentParser(
        description="Удалить старые файлы, когда использование диска превышает лимит."
    )

    parser.add_argument(
        "path", help="Путь к каталогу, в котором должны быть удалены файлы", type=str
    )
    parser.add_argument(
        "--порог",
        "-т",
        метавар = "Т",
        help="Порог использования в процентах",
        тип = целое,
        по умолчанию=80,
    )
    parser.add_argument(
        "--Удалить",
        "--рм",
        help="Файлы не удаляются, если не указан параметр --removed или --rm",
        действие = "store_true",
        по умолчанию = Ложь,
    )

    аргументы = парсер.parse_args()

    check_and_clean(
        аргументы.путь,
        порог=args.threshold,
        удалить=args.remove,
    )


если __name__ == "__main__":
    главный()

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

РЕДАКТИРОВАТЬ: я, наконец, добавил часть CLI в сценарий, думаю, я буду использовать ее сам.

qwr avatar
флаг kr
qwr
fwiw вы можете отправлять электронные письма из командной строки.
gcharbon avatar
флаг kz
Я не говорю, что это нельзя сделать в CLI, я говорю, что OP не знаком с bash, и ему может быть проще сделать это на Python.
флаг bv
Спасибо за исчерпывающий пост! Мне нравится более читаемый подход, но, к сожалению, я не так хорошо знаком с фитоном. Но вы заставили меня понять, что я могу сделать это с помощью php, с которым мне намного удобнее.
Clumsy cat avatar
флаг cn
+1 за что-то более легкое для тестирования. Я бы хотел, чтобы модульные тесты для такого скрипта.
Рейтинг:1
флаг tj

Проверить, если /dev/sbd/ достиг 90% мощности. Если это так, удалите самый старый файл (и файлы в подпапках) /home/бен/ftp/наблюдение/ И повторяйте это до тех пор, пока /dev/sbd/ емкость ниже 80% Повторяйте каждые 10 минут.

Сценарий ниже будет делать именно это (при условии, что вы добавите его в свой кронтаб работать с 10-минутными интервалами). Будьте особенно уверены, что это то, что вы действительно хотите сделать, так как это может легко стереть все файлы в /home/бен/ftp/наблюдение/ если ваш диск заполняется где-то за пределами этого каталога.

#!/бин/ш
directory='/home/ben/ftp/наблюдение'
максимальное_использование = 90
цель_использование = 80
[ -d "$каталог" ] || выход 1
[ "$max_usage" -gt "$goal_usage" ] || выход 1
[ "$( df --output=pcent $directory | \
    grep -Ewo '[0-9]+' )" -ge "$max_usage" ] || выход 0
dev_used="$( df -B 1K --output=использованный $каталог | \
    grep -Ewo '[0-9]+' )"
target_usage="$(printf "%.0f" \
    $(эхо ".01 * $goal_usage * \
    $( df -B 1K --output=размер каталога $ | \
        grep -Ewo '[0-9]+' )" | bc ))"
echo "$( find $directory -type f -printf '%Ts,%k,\047%p\047\n' )" | \
    сортировать -k1 | \
        awk -F, -v цель="$(($dev_used-$goal_usage))" '\
            (сумма+$2)>цель{printf "%s ",$3; выход} \
            (сумма+$2)<=goal{printf "%s",$3}; {сумма+=$2}' | \
                xargs пм

Как работает этот скрипт:

Первые 3 строки после shebang - это переменные в соответствии с вашими параметрами:

  • каталог — это полный путь к родительскому каталогу, содержащему файлы и подкаталоги, из которых вы хотите удалить старые файлы (т. е. /home/бен/ftp/наблюдение). Кавычки вокруг этого значения не нужны, если путь не содержит пробелов.
  • max_usage — это процент емкости диска, при которой будут выполняться действия по удалению старых файлов (т. е. 90 процент).
  • target_usage — это процент емкости диска, которую вы хотите получить после удаления старых файлов (т. е. 80 процент).

Обратите внимание, что значения max_usage и target_usage должно быть целые числа.

[ -d "$каталог" ] || выход 1
  • Проверяет, что каталог существует, в противном случае сценарий завершается и завершается со статусом 1.
[ "$max_usage" -gt "$goal_usage" ] || выход 1
  • Проверяет, что max_usage больше, чем target_usage, в противном случае скрипт завершается и выходит со статусом 1.
[ "$( df --output=pcent $directory | \
    grep -Ewo '[0-9]+' )" -ge "$max_usage" ] || выход 0
  • Получает текущий процент используемой емкости диска и проверяет, соответствует ли он пороговому значению, установленному max_usage. В противном случае дальнейшая обработка не требуется, поэтому сценарий завершается и выходит со статусом 0.
dev_used="$( df -B 1K --output=использованный $каталог | \
    grep -Ewo '[0-9]+' )"
  • Получает текущую используемую емкость диска в килобайтах.
target_usage="$(printf "%.0f" \
    $(эхо ".01 * $goal_usage * \
    $( df -B 1K --output=размер каталога $ | \
        grep -Ewo '[0-9]+' )" | bc ))"
  • Преобразует target_usage переменная в килобайтах (это значение нам понадобится ниже).
найти $directory -type f -printf '%Ts,%k,\047%p\047\n'
  • Находит все файлы в каталог (и во всех его подкаталогах) и составляет список этих файлов, по одному в строке, в формате отметка времени, размер в килобайтах, 'полный/путь/к/файлу'. Обратите внимание, что 'полный/путь/к/файлу' заключается в одинарные кавычки, поэтому пробелы в именах файлов или каталогов не вызовут проблем в дальнейшем.
сортировать -k1
  • Сортирует ранее эхо'd список файлов по отметке времени (сначала самые старые).
awk -F, -v цель = "$(($dev_used-$goal_usage))"
  • аук создает внутреннюю переменную Цель что равно разнице между dev_used и target_usage - и это общий объем файлов в килобайтах, которые необходимо удалить, чтобы снизить процент емкости диска до target_usage устанавливается в начале скрипта.
(сумма+$2)>цель{printf "%s ",$3; выход} \
(сумма+$2)<=goal{printf "%s",$3}; {сумма+=$2}'
  • аук (продолжение) начинает обработку списка, сохраняя текущую сумму значений поля 2 (размер в килобайтах) и печать значений поля 3 ('полный/путь/к/файлу') в строку, разделенную пробелами, пока сумма килобайтов из поля 2 не станет больше, чем Цель, в какой момент аук прекращает обработку дополнительных строк.
xargs пм
  • Строка значений «полный/путь/к/файлу» из аук передается в xargs который управляет г.м. команда, использующая строку в качестве аргумента. Это удаляет эти файлы.

Ответить или комментировать

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