Назад | Перейти на главную страницу

Добавление вывода в файл с одновременным усечением его до N строк

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

Вещи, на которые я смотрел для справки:

Я изучил расширенное руководство по написанию сценариев http://tldp.org/LDP/abs/html/io-redirection.html

Комбинированный вывод двух команд: Bash - Как вывести две команды в файл?

В идеале у меня было бы что-то вроде (я знаю, что это не работает)

(tail-n 1000 foo.log; ./foo.sh) > foo.log

который сохранит последние 1000 строк из моего текущего журнала, а затем добавит мой новый вывод для текущего запуска foo.sh.

Я не могу придумать способ использовать перенаправление добавления >> и ограничить исходный файл, не оборачивая вызов foo.sh в какой-то другой bar.sh

tail -n 1000 foo.log > tmp.log
mv tmp.log foo.log
./foo.sh >> foo.log

Это просто кажется глупым.

Возможно, мой ответ состоит в том, чтобы foo.sh не полагался на STDOUT как на место для отправки сообщений журнала, а скорее открывал файл напрямую.

ПОСЛЕДУЮЩИЕ ИЗМЕНЕНИЯ:

Преобладает мнение, что это не рекомендуется. Мнение, которое я ценю. Однако сервер, на котором это происходит, находится вне моего контроля и на самом деле не будет находиться под ... бдительным администратором. Это коробка, в которой множество разных групп владеют частями коробки, но никто не несет ответственности за общее состояние коробки. Я мог бы просто позволить журналу строиться вечно, и это, вероятно, не имело бы значения со временем, но я хотел бы сделать все, что в моих силах, чтобы управлять им, так как я знаю, что последний администратор ничего не сделает. Итак, использование crontab для запуска logrotate мне не подходит. Я просто ищу что-то, что я могу сделать с помощью ограниченного количества одной команды.

В общем, я бы рекомендовал вам не беспокоиться о размере журнала. Стандартный способ справиться с этим - использовать logrotate. Позвольте администраторам решить, насколько большим должен быть журнал. Я считаю, что часть хорошего * nix-приложения - это соблюдение общих стандартов: люди злятся, когда приложение делает что-то необычное.

Если ваша версия sed имеет --in-place переключатель, вы можете использовать это. Он по-прежнему cp и mv но это за кулисами, поэтому сценарий будет выглядеть немного менее беспорядочно в зависимости от того, как вы относитесь к sed синтаксис. В противном случае ваш tail (вы пропустили один в своем редактировании), и метод добавления - это то, как я подхожу к нему.

Другой подход с использованием sed будет держать ваш журнал вверх ногами с самой новой записью в начале.

sed -ni '1s/^/New Entry\n/;100q;p' logfile

Это все, что нужно для этого (кроме того, чтобы «Новая запись» была тем, что вы хотите). Это сохранит файл журнала из 100 строк в обратном хронологическом порядке. Преимущество состоит в том, что вам не нужно знать, сколько строк в файле (tail позаботится об этом за вас, но sed не будет). Это в некоторой степени предполагает, что ваши записи журнала состоят из одной строки. Если вы регистрируете значительный объем вывода из сценария, я думаю, что вы вернулись к tail и добавить.

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

#!/bin/bash
# *** UNTESTED ***
logdir="/path/to/your/log/dir"

# run the script and log its output to a file named with the date and time
script > "$logdir/$(date "+%Y%m%dT%H%M%S").log"

# maintain the logs
# keep the last 100 days of daily logfiles
# (you could keep the last 100 hours of hourly logfiles, for example,
# by using -mmin and doing some math)

# the count is intended to keep old entries from being deleted
# leaving you with nothing if there are no new entries
count=$(find "$logdir" -maxdepth 1 -type f | wc -l)
if (( count > 100 )) 
then
    find "$logdir" -maxdepth 1 -type f -mtime +100 -delete
fi

Это не рекомендуется, потому что нет возможности удалить первую строку файла без перезаписи всего файла. В зависимости от размера файла это интенсивная операция. Даже если вы действительно хотели это сделать, два метода (на месте с поиском или отключением, воссозданием) приведут к тому, что tail не увидит новые строки или не подумает, что все строки новые.

Я должен согласиться с Кайлом по поводу logrotate et al. Но если вы твердо настроены контролировать его из своего приложения, попробуйте поискать в Google что-нибудь вроде: файл кругового журнала Linux

Вот похожий пост, который дал мне два хороших ответа: Превращение файла журнала в своего рода кольцевой буфер

У меня была такая же проблема, и я с удовольствием использую ulogbufd.

Вот небольшой сценарий, который я использовал для этой цели.

Это позволяет избежать проблемы чтения и записи одновременно, создав два файла, myfile и myfile.old.

#!/bin/bash

# Bail on error
set -e

# Constants
DEFAULT_NUM_LINES="1000"

# Usage message
usage()
{
    echo "Usage: last-lines [-n num] filename" 1>&2
    echo "Options:" 1>&2
    echo "    -n    Specify number of lines file iteration (default ${DEFAULT_NUM_LINES})" 1>&2
    echo "    -h    Show this help message" 1>&2
}

# Parse flags passed in on the command line
NUM_LINES="${DEFAULT_NUM_LINES}"
while [ ${#} -gt 0 ]; do
    case "$1" in
        -n)
            shift
            NUM_LINES="${1}"
            shift
            ;;
        -h|--help)
            usage
            exit
            ;;
        --)
            shift
            break
            ;;
        *)
            break
            ;;
    esac
done
case "${#}" in
    1)
        FILENAME="$1"
        ;;
    *)
        usage
        exit 1
        ;;
esac

# Run
LINE_COUNT="0"
while read LINE; do
    echo "${LINE}" >> "${FILENAME}"
    LINE_COUNT=`expr "${LINE_COUNT}" + 1`
    if [ "${LINE_COUNT}" -ge "${NUM_LINES}" ]; then
        mv "${FILENAME}"{,.old}
        LINE_COUNT="0"
    fi
done