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

18

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?

dr01
la source
logrotateest la solution élégante
Ipor Sircer
1
J'y ai pensé, mais la configuration logrotate serait plus longue que le script lui-même ...
dr01
Si logrotate est exagéré, votre solution est à peu près aussi élégante que possible. Avec sed / awk, vous pourrez peut-être le faire sur une seule ligne mais pas sans fichier temporaire en interne, donc ce n'est probablement pas plus efficace et probablement moins lisible.
kba se tient avec Monica le

Réponses:

28

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 tail, cela va affecter l'utilisation de la mémoire du shell local pour stocker le contenu des lignes de journal.

Ce qui suit supprime également les lignes vides si elles existent à la fin du fichier journal (en raison du comportement de l'évaluation BASH "$(tail -1000 test.log)"), donc ne donne pas une troncature vraiment 100% précise dans tous les scénarios, mais selon 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
parkamark
la source
Intelligent. J'ai marqué cela comme la réponse acceptée car elle ne nécessite pas l'installation d'outils supplémentaires. J'aimerais pouvoir accepter à la fois la vôtre et la réponse de @ John1024.
dr01
Ton appel. J'ai voté pour la solution éponge car je ne le savais pas et il est garanti de ne pas salir les lignes de journal vides. Cette solution a le potentiel de le faire, selon le contenu du fichier journal.
parkamark
Cette solution a une condition de concurrence. Si vous n'avez pas de chance, la redirection -en- le fichier se produit avant la lecture -de- le fichier et vous vous retrouvez avec un fichier vide.
Coroos Il y a
21

L'utilitaire spongeest 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. spongerésout ce problème en n'écrivant myscript.logqu'après avoir tailfini de le lire et terminé le tuyau.

Installer

Pour installer spongesur un système de type Debian:

apt-get install moreutils

Pour installer spongesur un système RHEL / CentOS, ajoutez le référentiel EPEL puis procédez comme suit:

yum install moreutils

Documentation

De man sponge:

spongelit l'entrée standard et l'écrit dans le fichier spécifié. Contrairement à une redirection shell, spongeabsorbe 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.

John1024
la source
2
+1 Merci, je ne savais pas sponge. Très utile pour tous ceux qui ont appris à la dure ce que vous ne pouvez pas faire sort importantfile.txt > importantfile.txt:)
dr01
4

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

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

Pour mémoire, edvous pourriez faire quelque chose comme

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

Cela ouvre infileet rlit 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. Remplacez ,ppar wpour modifier le fichier sur place.
Gardez à l'esprit que les edsolutions ne conviennent pas aux fichiers volumineux.

don_crissti
la source
0

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.1et logfileest renommé en 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 à 1000 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à 1000, effectuez la rotation immédiatement).

Ou vous pouvez obtenir la taille, comme avec wc -c logfileet faire la rotation basée sur le dépassement d'une certaine taille. De cette façon, le fichier n'a jamais à être analysé pour déterminer la condition.

Kaz
la source
0

J'ai utilisé, au lieu de mv, la cpcommande pour y parvenir, vous pouvez 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 en un seul endroit sous forme de liens physiques. Si vous utilisez la mvcommande, vous perdez le lien dur. Si vous utilisez cpplutôt la commande, 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 sont sur le même système de fichiers, vous pouvez également accorder des droits différents aux utilisateurs et ${LOGFILE_DIR}modifier la longueur comme je le fais.

Si c'est la mvcommande, 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 ne permettez pas à quelqu'un d'effacer le fichier, vos journaux restent ensemble et sont bien contrôlés via votre propre script.

logrotatepeut-être plus agréable. 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 bien.

Par exemple, il existe un répertoire dans lequel les fichiers plus anciens sont automatiquement zippés dans un fichier OLDFILE.zipet tout ce qui est compressé est également répertorié dans le fichier, de .zip_logsorte qu'il se .zip_logtrouve également dans ce répertoire, mais dans le fichier LOGFILE_DIRque j'ai avec:

ln .zip_log "${LOGFILE_DIR}/USER_ZIP_log"

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

Andreas Bartels
la source