Как сохранить только последние n строк файла журнала?

18

Сценарий, который я написал, что-то делает и в конце добавляет несколько строк в свой лог-файл. Я хотел бы сохранить только последние n строк (скажем, 1000 строк) файла журнала. Это можно сделать в конце скрипта следующим образом:

tail -n 1000 myscript.log > myscript.log.tmp
mv -f myscript.log.tmp myscript.log

но есть ли более чистое и элегантное решение? Возможно, выполнено с помощью одной команды?

dr01
источник
logrotateэто элегантное решение
Ipor Sircer
1
Я думал об этом, но конфигурация logrotate будет длиннее, чем сам скрипт ...
dr01
Если logrotate является излишним, ваше решение будет настолько элегантным, насколько оно возможно. С sed / awk вы можете сделать это в одну строку, но не без временного файла внутри, поэтому он, вероятно, не более эффективен и, вероятно, менее читабелен.
КБА стоит с Моникой

Ответы:

28

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

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

Приведенное ниже также удаляет пустые строки, если они существуют в конце файла журнала (из-за поведения оценки BASH "$(tail -1000 test.log)"), поэтому не дает действительно 100% точного усечения во всех сценариях, но в зависимости от вашей ситуации может быть достаточно.

$ wc -l myscript.log
475494 myscript.log

$ echo "$(tail -1000 myscript.log)" > myscript.log

$ wc -l myscript.log
1000 myscript.log
parkamark
источник
Умный. Я отметил это как принятый ответ, так как он не требует установки дополнительных инструментов. Хотел бы я принять и ваш, и ответ @ John1024.
Dr01
Ваш звонок. Я проголосовал за решение «губка», так как не знал об этом, и он гарантированно не возиться с пустыми строками журнала. Это решение может сделать это в зависимости от содержимого файла журнала.
Parkamark
Это решение имеет состояние гонки. Если вам не повезло, перенаправление в файл происходит до чтения из файла, и в итоге вы получаете пустой файл.
Короос
21

Утилита spongeпредназначена как раз для этого случая. Если он установлен, то две ваши строки могут быть записаны:

tail -n 1000 myscript.log | sponge myscript.log

Обычно чтение из файла одновременно с записью в нем является ненадежным. spongeрешает это, не записывая до myscript.logтех пор, пока tailне закончит чтение и не завершит конвейер.

устанавливать

Для установки spongeна Debian-подобную систему:

apt-get install moreutils

Для установки spongeв системе RHEL / CentOS добавьте репозиторий EPEL и выполните:

yum install moreutils

Документация

От man sponge:

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

John1024
источник
2
+1 Спасибо, я не знал sponge. Очень полезно для всех тех, кто выучил трудный путь, который вы не можете сделать sort importantfile.txt > importantfile.txt:)
dr01
4

определенно "хвост + мв" намного лучше! Но для GNU SED мы можем попробовать

sed -i -e :a -e '$q;N;101,$D;ba' log
JJoao
источник
3

Для записи, с edвами можно сделать что-то вроде

ed -s infile <<\IN
0r !tail -n 1000 infile
+1,$d
,p
q
IN

Это открывает infileи rзавершает вывод tail -n 1000 infile(т. Е. Вставляет этот вывод перед 1-й строкой), а затем удаляет то, что было изначально 1-й строкой, до конца файла. Замените ,pна wдля редактирования файла на месте.
Имейте в виду, что edрешения не подходят для больших файлов.

don_crissti
источник
0

Что вы можете сделать в своем скрипте - это реализовать логику ротации журналов. Сделайте всю регистрацию через функцию:

log()
{
   ...
}

Эта функция, во-первых, делает что-то вроде:

printf "%s\n" "$*" >> logfile

затем он проверяет размер файла или каким-то образом решает, что файл требует ротации. В этот момент файл logfile.1, если он существует, удаляется, файл logfile.0, если он существует, переименовывается logfile.1и logfileпереименовывается logfile.0.

Решение о том, вращать ли, может быть основано на счетчике, поддерживаемом в самом скрипте. Когда он достигает 1000, он сбрасывается в ноль.

Если всегда требуется строго обрезать до 1000 строк, сценарий может подсчитать количество строк в файле журнала при запуске и соответствующим образом инициализировать счетчик (или, если число уже соответствует или превышает 1000, немедленно выполните ротацию).

Или вы можете получить размер, например, с помощью wc -c logfileи сделать поворот на основе превышения определенного размера. Таким образом, файл никогда не нужно сканировать, чтобы определить условие.

Kaz
источник
0

Я сделал использовать, вместо того mv, то cpкоманду для достижения этой цели , что вы можете иметь некоторые лога прямо на месте , где программное обеспечение работает. Может быть, в другом домашнем каталоге пользователя или в каталоге приложения, и все журналы находятся в одном месте в виде жестких ссылок. Если вы используете mvкоманду, вы теряете жесткую ссылку. Если вы используете cpкоманду вместо этого, вы сохраните эту жесткую ссылку.

мой код что-то вроде:

TMP_FILE="$(mktemp "${TMPFILENAME}.XXX")"

for FILE in "${LOGFILE_DIR}"/* ; do
    tail -n $MAXLINES "${FILE}" > "${TMP_FILE}"
    if [ $(ls -g "${TMP_FILE}" | awk '{print $4}') -lt $(ls -g "${FILE}" | awk '{print $4}') ] ; then
        cp "${TMP_FILE}" "${FILE}"
    fi
done   

Поэтому, если файлы находятся в одной и той же файловой системе, вы также можете предоставить пользователям несколько разных прав, а в случае ${LOGFILE_DIR}изменения длины, как я.

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

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

logrotateможет быть, лучше. Но я доволен этим решением.

Не беспокойтесь "", но в моем случае есть некоторые файлы с пробелами и другими специальными буквами, и если я не делаю "" вокруг или {}, то вся партия работает неправильно.

Например, есть каталог, в котором более старые файлы автоматически архивируются в архив, OLDFILE.zipи все, что архивируется, также указано в файле, .zip_logпоэтому он также .zip_logнаходится в этом каталоге, но в разделе « LOGFILE_DIRУ меня есть»:

ln .zip_log "${LOGFILE_DIR}/USER_ZIP_log"

равный файл, так как это жесткая ссылка.

Андреас Бартельс
источник