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?
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
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.
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
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.
certainement "tail + mv" est beaucoup mieux! Mais pour gnu sed on peut essayer
sed -i -e :a -e '$q;N;101,$D;ba' log
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 r
eads 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.
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"
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.
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.