web-dev-qa-db-fra.com

Tronquer un fichier en cours d'utilisation (Linux)

J'ai un processus qui écrit beaucoup de données sur stdout, que je redirige vers un fichier journal. Je voudrais limiter la taille du fichier en copiant occasionnellement le fichier actuel sous un nouveau nom et en le tronquant.

Mes techniques habituelles de tronquer un fichier, comme

cp /dev/null file

ne fonctionnent pas, probablement parce que le processus l'utilise.

Existe-t-il un moyen de tronquer le fichier? Ou le supprimer et associer en quelque sorte la sortie standard du processus à un nouveau fichier?

FWIW, c'est un produit tiers que je ne peux pas modifier pour changer son modèle de journalisation.

EDIT la redirection sur le fichier semble avoir le même problème que la copie ci-dessus - le fichier reprend sa taille précédente la prochaine fois qu'il est écrit dans:

ls -l sample.log ; echo > sample.log ; ls -l sample.log ; sleep 10 ; ls -l sample.log
-rw-rw-r-- 1 user group 1291999 Jun 11  2009 sample.log
-rw-rw-r-- 1 user group 1 Jun 11  2009 sample.log
-rw-rw-r-- 1 user group 1292311 Jun 11  2009 sample.log
34
Hobo

Jetez un œil à l'utilitaire split(1), qui fait partie de GNU Coreutils.

10
Michiel Buddingh

Depuis coreutils 7.0, il existe une commande truncate.

31
Peter Eisentraut

La chose intéressante à propos de ces fichiers régénérés est que les premiers 128 Ko environ seront tous des zéros après avoir tronqué le fichier en copiant /dev/null par-dessus. Cela se produit car le fichier est tronqué à une longueur nulle, mais le descripteur de fichier dans l'application pointe toujours immédiatement après sa dernière écriture. Lorsqu'il réécrit, le système de fichiers traite le début du fichier comme zéro octet - sans réellement écrire les zéros sur le disque.

Idéalement, vous devriez demander au fournisseur de l'application d'ouvrir le fichier journal avec le O_APPEND drapeau. Cela signifie qu'après avoir tronqué le fichier, la prochaine écriture cherchera implicitement à la fin du fichier (c'est-à-dire revenir à zéro), puis rédigera les nouvelles informations.


Ce code monte la sortie standard, il est donc dans O_APPEND mode puis invoque la commande donnée par ses arguments (un peu comme Nice exécute une commande après avoir ajusté son niveau de Nice, ou Nohup exécute une commande après avoir corrigé les choses pour ignorer SIGHUP).

#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>

static char *arg0 = "<unknown>";

static void error(const char *fmt, ...)
{
    va_list args;
    int errnum = errno;
    fprintf(stderr, "%s: ", arg0);
    va_start(args, fmt);
    vfprintf(stderr, fmt, args);
    va_end(args);
    if (errnum != 0)
        fprintf(stderr, " (%d: %s)", errnum, strerror(errnum));
    putc('\n', stderr);
    fflush(0);
    exit(1);
}

int main(int argc, char **argv)
{
    int attr;
    arg0 = argv[0];

    if (argc < 2)
        error("Usage: %s cmd [arg ...]", arg0);
    if ((attr = fcntl(1, F_GETFL, &attr)) < 0)
        error("fcntl(F_GETFL) failed");
    attr |= O_APPEND;
    if (fcntl(1, F_SETFL, attr) != 0)
        error("fcntl(F_SETFL) failed");
    execvp(argv[1], &argv[1]);
    error("failed to exec %s", argv[1]);
    return(1);
}

Mes tests ont été quelque peu décontractés, mais à peine suffisants pour me convaincre que cela a fonctionné.


Alternative plus simple

Billy note dans son réponse que '>> 'est l'opérateur append - et en effet, sur Solaris 10, bash (version 3.00.16 (1)) utilise le O_APPEND flag - rendant ainsi le code ci-dessus inutile, comme indiqué ('Black JL:' est mon invite sur cette machine):

Black JL: truss -o bash.truss bash -c "echo Hi >> x3.29"
Black JL: grep open bash.truss
open("/var/ld/ld.config", O_RDONLY)             Err#2 ENOENT
open("/usr/lib/libcurses.so.1", O_RDONLY)       = 3
open("/usr/lib/libsocket.so.1", O_RDONLY)       = 3
open("/usr/lib/libnsl.so.1", O_RDONLY)          = 3
open("/usr/lib/libdl.so.1", O_RDONLY)           = 3
open("/usr/lib/libc.so.1", O_RDONLY)            = 3
open("/platform/SUNW,Ultra-4/lib/libc_psr.so.1", O_RDONLY) = 3
open64("/dev/tty", O_RDWR|O_NONBLOCK)           = 3
stat64("/usr/openssl/v0.9.8e/bin/bash", 0xFFBFF2A8) Err#2 ENOENT
open64("x3.29", O_WRONLY|O_APPEND|O_CREAT, 0666) = 3
Black JL:

Utilisez la redirection d'ajout plutôt que le code wrapper (' cantrip ') ci-dessus. Cela montre simplement que lorsque vous utilisez une technique particulière à d'autres fins (valides), l'adapter à une autre n'est pas nécessairement le mécanisme le plus simple - même si cela fonctionne.

26
Jonathan Leffler

Redirigez la sortie en utilisant >> au lieu de>. Cela vous permettra de tronquer le fichier sans que le fichier repasse à sa taille d'origine. N'oubliez pas non plus de rediriger STDERR (2> & 1).

Le résultat final serait donc: myprogram >> myprogram.log 2>&1 &

12
Jono

Essayez > file.


Mise à jour concernant les commentaires: cela fonctionne bien pour moi:

robert@rm:~> echo "content" > test-file
robert@rm:~> cat test-file 
content
robert@rm:~> > test-file
robert@rm:~> cat test-file 
8
Robert Munteanu

J'ai eu un problème similaire sur redhat v6, echo > file ou > file entraînait la défaillance d'Apache et de Tomcat car les fichiers journaux devenaient inaccessibles pour eux.

Et la solution était étrange

echo " " > file

nettoierait le fichier et ne causerait aucun problème.

6
aishu

J'ai téléchargé et compilé la dernière coreutils pour pouvoir disposer de truncate.

Ran ./configure et make, mais n'a pas exécuté make install.

Tous les utilitaires compilés apparaissent dans le dossier "src".

J'ai couru

[path]/src/truncate -s 1024000 textfileineedtotruncate.log

sur un fichier journal de 1,7 Go.

Il n'a pas modifié la taille indiquée lors de l'utilisation de ls -l, mais cela a libéré tout l'espace disque - ce que j'avais vraiment besoin de faire avant /var rempli et tué le processus.

Merci pour l'astuce sur "tronquer"!

3
Larry Irwin

Sous Linux (en fait, toutes les unités), les fichiers sont créés lorsqu'ils sont ouverts et supprimés lorsque rien ne contient une référence à eux. Dans ce cas, le programme qui l'a ouvert et le répertoire dans lequel il a été ouvert contiennent des références au fichier. Lorsque le programme cp veut écrire dans le fichier, il obtient une référence dans le répertoire, écrit une longueur de zéro dans les métadonnées stockées dans le répertoire (c'est une légère simplification) et abandonne le handle. Ensuite, le programme d'origine, toujours en tenant le descripteur de fichier d'origine, écrit quelques données supplémentaires dans le fichier et enregistre ce qu'il pense que la longueur devrait être.

même si vous supprimez le fichier du répertoire, le programme continuera à y écrire des données (et à utiliser de l'espace disque) même si aucun autre programme n'aurait le moindre moyen de le référencer.

en bref, une fois que le programme a une référence (handle) vers un fichier, rien de ce que vous faites ne changera cela.

il existe en théorie des moyens de modifier le comportement des programmes en définissant LD_LIBRARY_PATH pour inclure un programme qui intercepte tous les appels du système d'accès aux fichiers. Je me souviens avoir vu quelque chose comme ça quelque part, mais je ne me souviens pas du nom.

3
Arthur Ulfeldt

lorsque le fichier est utilisé, si vous essayez de l'annuler ou quelque chose comme ça, il peut parfois "confondre" l'application qui écrit dans le fichier journal et ne rien enregistrer après cela.

Ce que j'essaierais de faire, c'est de mettre en place une sorte de proxy/filtre pour ce journal, au lieu de rediriger vers un fichier, de rediriger vers un processus ou quelque chose qui obtiendrait une entrée et une écriture dans un fichier tournant.

Peut-être que cela peut être fait par script, sinon vous pouvez écrire une application simple pour cela (Java ou autre). L'impact sur les performances de l'application devrait être assez faible, mais vous devrez en exécuter tests.

Btw, votre application, est-ce une application Web autonome, ...? Il y a peut-être d'autres options à étudier.

Edit: il y a aussi un Append Redirection Operator >> que je n'ai jamais utilisé personnellement, mais il pourrait ne pas verrouiller le fichier.

3
Billy

@Hobo use freopen (), il réutilise le flux pour ouvrir le fichier spécifié par nom de fichier ou pour changer son mode d'accès. Si un nouveau nom de fichier est spécifié, la fonction tente d'abord de fermer tout fichier déjà associé au flux (troisième paramètre) et le dissocie. Ensuite, indépendamment de la fermeture ou non de ce flux, freopen ouvre le fichier spécifié par nom de fichier et l'associe au flux comme le ferait fopen en utilisant le mode spécifié.

si un binaire tiers génère des journaux, nous devons écrire un wrapper qui fera tourner les journaux, et un tiers s'exécutera dans le thread proxyrun comme ci-dessous.

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <unistd.h>
#include <string.h>

using namespace std;

extern "C" void * proxyrun(void * pArg){
   static int lsiLineNum = 0;
   while(1) 
   {
     printf("\nLOGGER: %d",++lsiLineNum);
     fflush(stdout);
   }
  return NULL;
}


int main(int argc, char **argv)
{
  pthread_t lThdId;
  if(0 != pthread_create(&lThdId, NULL, proxyrun, NULL))
  {
    return 1;
  }

  char lpcFileName[256] = {0,};

  static int x = 0;

  while(1)
  {
    printf("\n<<<MAIN SLEEP>>>");
    fflush(stdout);
    sprintf(lpcFileName, "/home/yogesh/C++TestPrograms/std.txt%d",++x);
    freopen(lpcFileName,"w",stdout);
    sleep(10);
  }

  return 0;
}
2
Yogesh

Avez-vous vérifié le comportement de signaux tels que SIGHUP vers le produit tiers, pour voir s'il commencera à enregistrer un nouveau fichier? Vous devez d'abord déplacer l'ancien fichier vers un nom permanent.

kill -HUP [id-processus]

Et puis ça recommencerait à écrire.

Alternativement (comme l'a suggéré Billy) peut-être rediriger la sortie de l'application vers un programme de journalisation comme multilog ou celui qui est couramment utilisé avec Apache, connu sous le nom de cronolog. Ensuite, vous aurez un contrôle plus fin de tout ce qui se passe avant qu'il ne soit écrit dans ce descripteur de fichier initial (fichier), ce qui est vraiment tout.

2
Vex

J'ai eu un problème similaire et je n'ai pas pu faire de "tail -f" sur la sortie d'un script exécuté à partir de cron:

    * * * * * my_script >> /var/log/my_script.log 2>&1

Je l'ai corrigé en changeant la redirection stderr:

    * * * * * my_script >> /var/log/my_script.log 2>/var/log/my_script.err
1
lfjeff

au lieu de le rediriger vers un fichier, vous pouvez le diriger vers un programme qui fait automatiquement pivoter le fichier en le fermant, en le déplaçant et en ouvrant un nouveau chaque fois qu'il devient trop gros.

1
Arthur Ulfeldt