web-dev-qa-db-fra.com

Comment conservez-vous uniquement les n dernières lignes d'un fichier journal?

Un script que j'ai écrit fait quelque chose et, à la fin, ajoute quelques lignes à son propre fichier journal. Je souhaite conserver uniquement les n dernières lignes (disons, 1 000 lignes) du fichier journal. Cela peut être fait à la fin du script de cette façon:

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

mais existe-t-il une solution plus propre et élégante? Peut-être accompli via une seule commande?

23
dr_

C'est possible comme ça, mais comme d'autres l'ont dit, l'option la plus sûre est la génération d'un nouveau fichier, puis un déplacement de ce fichier pour écraser l'original.

La méthode ci-dessous charge les lignes dans BASH, donc en fonction du nombre de lignes de tail, cela va affecter l'utilisation de la mémoire du shell local pour stocker le contenu des lignes de journal.

Le ci-dessous supprime également les lignes vides si elles existent à la fin du fichier journal (en raison du comportement de BASH évaluant "$(tail -1000 test.log)") ne donne donc pas une troncature vraiment 100% précise dans tous les scénarios, mais en fonction de votre situation, peut être suffisant.

$ wc -l myscript.log
475494 myscript.log

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

$ wc -l myscript.log
1000 myscript.log
29
parkamark

L'utilitaire sponge est conçu uniquement pour ce cas. Si vous l'avez installé, vos deux lignes peuvent s'écrire:

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

Normalement, la lecture d'un fichier en même temps que vous y écrivez n'est pas fiable. sponge résout ce problème en n'écrivant pas à myscript.log jusqu'à ce que tail ait fini de le lire et ait terminé le canal.

Installer

Pour installer sponge sur un système de type Debian:

apt-get install moreutils

Pour installer sponge sur un système RHEL/CentOS, ajoutez le dépôt EPEL puis procédez comme suit:

yum install moreutils

Documentation

De man sponge:

sponge lit l'entrée standard et l'écrit dans le fichier spécifié. Contrairement à une redirection Shell, sponge absorbe toutes ses entrées avant d'écrire le fichier de sortie. Cela permet de construire des pipelines qui lisent et écrivent dans le même fichier.

24
John1024

certainement "tail + mv" est beaucoup mieux! Mais pour gnu sed on peut essayer

sed -i -e :a -e '$q;N;101,$D;ba' log
5
JJoao

Pour mémoire, avec ed vous pourriez faire quelque chose comme

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

Cela ouvre infile et reads dans la sortie de tail -n 1000 infile (c'est-à-dire qu'il insère cette sortie avant la 1ère ligne), puis supprime de ce qui était initialement la 1ère ligne jusqu'à la fin du fichier. Remplacer ,p avec w pour modifier le fichier sur place.
Gardez à l'esprit que les solutions ed ne conviennent pas aux fichiers volumineux.

3
don_crissti

Si un autre processus met à jour le fichier journal et que vous souhaitez éviter de l'interférer, votre meilleur pari est peut-être de conserver un deuxième fichier. Le deuxième fichier (appelons-le "fichier d'archive") contiendra les dernières n lignes enregistrées.

Pour ce faire le plus proprement possible, déplacez d'abord le fichier journal existant vers un emplacement temporaire. Combinez-le ensuite avec le fichier d'archive précédemment enregistré et conservez jusqu'à n lignes.

Quant au processus qui se connecte, il doit simplement démarrer un nouveau fichier la prochaine fois qu'il écrit un message de journal.

#!/bin/bash

log_dir=/path/to/my/logs
log_filename=my_file.log
lines_to_keep=1000
archive_filename="${log_filename}.001"

work_dir="$(mktemp -d -p "$logdir")" && \
    touch "$log_dir/$archive_filename" && \
    touch "$log_dir/$log_filename" && \
    mv "$log_dir/$archive_filename" "$work_dir/" && \
    mv "$log_dir/$log_filename" "$work_dir/" && \
    cat "$work_dir/$archive_filename" "$work_dir/$log_filename" | \
        tail -n $lines_to_keep > "$log_dir/$archive_filename" && \
    rm "$work_dir/$archive_filename" && \
    rm "$work_dir/$log_filename" && \
    rmdir "$work_dir"
0
Bryan Roach

Ce que vous pouvez faire dans votre script, c'est implémenter la logique de rotation des journaux. Faites toute la journalisation via une fonction:

log()
{
   ...
}

Cette fonction, premièrement, fait quelque chose comme:

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

ensuite, il vérifie la taille du fichier ou décide en quelque sorte que le fichier nécessite une rotation. À ce stade, le fichier logfile.1, s'il existe, est supprimé, le fichier logfile.0, s'il existe, est renommé logfile.1 et logfile est renommé logfile.0.

La décision de faire une rotation peut être basée sur un compteur maintenu dans le script lui-même. Lorsqu'il atteint 1000, il est remis à zéro.

S'il est toujours nécessaire de limiter strictement à 1 000 lignes, le script peut compter le nombre de lignes dans le fichier journal au démarrage et initialiser le compteur en conséquence (ou si le nombre atteint ou dépasse déjà 1 000, effectuez la rotation immédiatement).

Ou vous pouvez obtenir la taille, comme avec wc -c logfile et faites la rotation en fonction du dépassement d'une certaine taille. De cette façon, le fichier n'a jamais à être analysé pour déterminer la condition.

0
Kaz

J'ai utilisé, au lieu de mv, la commande cp pour obtenir que vous puissiez avoir des fichiers journaux à l'endroit où un logiciel est en cours d'exécution. Peut-être dans le répertoire d'accueil de l'utilisateur différent ou dans le répertoire d'application et que tous les journaux sont regroupés au même endroit sous forme de liens physiques. Si vous utilisez la commande mv, vous perdez le lien dur. Si vous utilisez plutôt la commande cp, vous conserverez ce lien dur.

mon code est quelque chose comme:

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   

Donc, si les fichiers se trouvent sur le même système de fichiers, vous pouvez également accorder des droits différents aux utilisateurs et dans le ${LOGFILE_DIR} vous modifiez la longueur comme je le fais.

Si c'est la commande mv vous perdez le lien dur entre les fichiers et donc votre deuxième fichier n'est plus connecté au premier - peut-être placé quelque part ailleurs.

Si à l'autre endroit vous n'autorisez pas quelqu'un à effacer le fichier, vos journaux restent ensemble et sont bien contrôlés via votre propre script.

logrotate peut-être mieux. Mais je suis satisfait de cette solution.

Ne soyez pas dérangé par le "" mais dans mon cas il y a des fichiers avec des espaces et d'autres lettres spéciales dans et si je ne fais pas le "" autour ou le {} le tout ne fonctionne pas Nice.

Par exemple, il existe un répertoire dans lequel les fichiers plus anciens sont automatiquement compressés dans un OLDFILE.Zip et tout ce qui est compressé est également répertorié dans le fichier .Zip_log alors le .Zip_log se trouve également dans ce répertoire, mais dans le LOGFILE_DIR J'ai avec:

ln .Zip_log "${LOGFILE_DIR}/USER_Zip_log"

le fichier égal car il s'agit d'un lien dur.

0
Andreas Bartels