627 votes

Comment exécuter une commande à chaque fois qu'un fichier est modifié ?

Je veux un moyen simple et rapide d'exécuter une commande à chaque fois qu'un fichier est modifié. Je veux quelque chose de très simple, quelque chose que je laisserai tourner sur un terminal et que je fermerai dès que j'aurai fini de travailler sur ce fichier.

Actuellement, j'utilise ceci :

while read; do ./myfile.py ; done

Et ensuite je dois aller dans ce terminal et appuyer sur Enter chaque fois que j'enregistre ce fichier sur mon éditeur. Ce que je veux, c'est quelque chose comme ça :

while sleep_until_file_has_changed myfile.py ; do ./myfile.py ; done

Ou toute autre solution aussi simple que cela.

BTW : J'utilise Vim, et je sais que je peux ajouter une autocommande pour exécuter quelque chose sur BufWrite, mais ce n'est pas le genre de solution que je veux maintenant.

Mise à jour : Je veux quelque chose de simple, jetable si possible. De plus, je veux quelque chose qui s'exécute dans un terminal car je veux voir la sortie du programme (je veux voir les messages d'erreur).

A propos des réponses : Merci pour toutes vos réponses ! Elles sont toutes très bonnes, et chacune adopte une approche très différente des autres. Comme je ne dois en accepter qu'une seule, j'accepte celle que j'ai effectivement utilisée (elle était simple, rapide et facile à mémoriser), même si je sais qu'elle n'est pas la plus élégante.

0 votes

Possibilité de duplication de site croisé de : stackoverflow.com/questions/2972765/ ( bien qu'ici c'est le sujet =) )

0 votes

J'ai déjà référencé un duplicate cross site et il a été refusé :S ;)

4 votes

La solution de Jonathan Hartley s'appuie sur d'autres solutions proposées ici et corrige les gros problèmes des réponses les plus votées : absence de certaines modifications et inefficacité. Veuillez changer la réponse acceptée par la sienne, qui est également maintenue sur github à l'adresse suivante github.com/tartley/rerun2 (ou à une autre solution sans ces défauts)

555voto

Simple, utilisant inotifywait (installez le logiciel de votre distribution inotify-tools paquet) :

while inotifywait -e close_write myfile.py; do ./myfile.py; done

ou

inotifywait -q -m -e close_write myfile.py |
while read -r filename event; do
  ./myfile.py         # or "./$filename"
done

Le premier extrait est plus simple, mais il présente un inconvénient de taille : il manquera les modifications effectuées pendant que inotifywait n'est pas en cours d'exécution (en particulier lorsque myfile est en cours). Le deuxième extrait n'a pas ce défaut. Cependant, il faut savoir qu'il suppose que le nom du fichier ne contient pas d'espace. Si c'est un problème, utilisez la fonction --format pour que la sortie n'inclue pas le nom du fichier :

inotifywait -q -m -e close_write --format %e myfile.py |
while read events; do
  ./myfile.py
done

Quoi qu'il en soit, il y a une limitation : si un programme remplace myfile.py avec un fichier différent, plutôt que d'écrire dans le fichier existant. myfile , inotifywait mourront. Beaucoup d'éditeurs travaillent de cette façon.

Pour surmonter cette limitation, utilisez inotifywait sur le répertoire :

inotifywait -e close_write,moved_to,create -m . |
while read -r directory events filename; do
  if [ "$filename" = "myfile.py" ]; then
    ./myfile.py
  fi
done

Vous pouvez également utiliser un autre outil qui utilise la même fonctionnalité sous-jacente, par exemple incron (vous permet d'enregistrer des événements lorsqu'un fichier est modifié) ou fswatch (un outil qui fonctionne également sur de nombreuses autres variantes d'Unix, en utilisant l'analogue de l'inotify de Linux pour chaque variante).

54 votes

J'ai encapsulé tout cela (avec quelques astuces de bash) dans un programme simple à utiliser. sleep_until_modified.sh script, disponible sur : bitbucket.org/denilsonsa/small_scripts/src

15 votes

while sleep_until_modified.sh derivation.tex ; do latexmk -pdf derivation.tex ; done est fantastique. Merci.

1 votes

inotifywait ne joue pas bien avec les fichiers temporaires. Si vous enregistrez un fichier avec vim ( :w ) vous obtiendrez 2 CREATE y DELETE ainsi que des signaux 2 MOVE signaux. stackoverflow.com/questions/10527936/

256voto

Paul Fenney Points 2451

entr ( http://entrproject.org/ ) fournit une interface plus conviviale pour inotify (et supporte également *BSD & Mac OS X).

Il est très facile de spécifier plusieurs fichiers à regarder (limité seulement par ulimit -n ), évite de s'occuper des fichiers qui sont remplacés, et nécessite moins de syntaxe bash :

$ find . -name '*.py' | entr ./myfile.py

Je l'ai utilisé sur l'ensemble de l'arbre source de mon projet pour exécuter les tests unitaires du code que je suis en train de modifier, et cela a déjà considérablement amélioré mon flux de travail.

Des drapeaux comme -c (effacer l'écran entre les passages) et -d (sortie lorsqu'un nouveau fichier est ajouté à un répertoire surveillé) ajoutent encore plus de flexibilité, par exemple vous pouvez faire :

$ while sleep 1 ; do find . -name '*.py' | entr -d ./myfile.py ; done

Au début de l'année 2018, il est toujours en développement actif et on peut le trouver dans Debian et Ubuntu ( apt install entr ) ; la construction à partir du dépôt de l'auteur s'est faite sans problème dans tous les cas.

9 votes

Ne gère pas les nouveaux fichiers et leurs modifications.

3 votes

@Wernight - à partir du 7 mai 2014 entr a le nouveau -d c'est un peu plus long, mais vous pouvez faire ce qui suit while sleep 1 ; do find . -name '*.py' | entr -d ./myfile.py ; done pour traiter les nouveaux dossiers.

0 votes

Avec le -d il est alors possible de manquer certains changements.

146voto

joh Points 1553

J'ai écrit un programme Python pour faire exactement cela, appelé lorsqu'il a changé .

L'utilisation est simple :

when-changed FILE COMMAND...

Ou pour regarder plusieurs fichiers :

when-changed FILE [FILE ...] -c COMMAND

FILE peut être un répertoire. Regarder récursivement avec -r . Utilisez %f pour passer le nom du fichier à la commande.

0 votes

Cela fonctionne-t-il lorsque le fichier est remplacé comme le font les éditeurs ?

1 votes

@ysangkok oui, dans la dernière version du code :)

0 votes

+1, très bien. ruby a watchr mais ceci est plus générique dans la mesure où il fonctionne pour les commandes bash. Peut-être pourriez-vous ajouter un drapeau qui lui permette d'être le miroir de watch de bash, c'est-à-dire effacer l'écran à chaque fois, et peut-être montrer le dernier temps exécuté

72voto

VDR Points 981

Que pensez-vous de ce script ? Il utilise le stat pour obtenir le temps d'accès d'un fichier et lance une commande chaque fois qu'il y a un changement dans le temps d'accès (chaque fois que le fichier est accédé).

#!/bin/bash

### Set initial time of file
LTIME=`stat -c %Z /path/to/the/file.txt`

while true    
do
   ATIME=`stat -c %Z /path/to/the/file.txt`

   if [[ "$ATIME" != "$LTIME" ]]
   then    
       echo "RUN COMMAND"
       LTIME=$ATIME
   fi
   sleep 5
done

4 votes

N'est-ce pas ? stat L'heure de modification serait-elle une meilleure réponse "chaque fois qu'un fichier est modifié" ?

1 votes

L'exécution de stat plusieurs fois par seconde entraînerait-elle de nombreuses lectures sur le disque ? Ou l'appel système fstat mettrait-il automatiquement en cache ces réponses d'une manière ou d'une autre ? J'essaie d'écrire une sorte de 'grunt watch' pour compiler mon code C à chaque fois que je fais des changements.

0 votes

C'est une bonne solution si vous connaissez à l'avance le nom du fichier à surveiller. Le mieux serait de passer le nom de fichier au script. Mieux encore serait de pouvoir passer plusieurs noms de fichiers (par exemple "mywatch *.py"). Mieux encore serait s'il pouvait opérer récursivement sur les fichiers dans les sous-répertoires aussi, ce que certaines des autres solutions font.

46voto

LiamB Points 7105

Pour ceux qui ne peuvent pas installer inotify-tools comme moi, cela devrait être utile :

watch -d -t -g ls -lR

Cette commande se termine lorsque la sortie change, ls -lR listera chaque fichier et répertoire avec sa taille et ses dates, donc si un fichier est modifié, la commande devrait sortir, comme le dit man :

-g, --chgexit
          Exit when the output of command changes.

Je sais que cette réponse ne sera peut-être lue par personne, mais j'espère que quelqu'un y accédera.

Exemple de ligne de commande :

~ $ cd /tmp
~ $ watch -d -t -g ls -lR && echo "1,2,3"

Ouvrez un autre terminal :

~ $ echo "testing" > /tmp/test

Maintenant le premier terminal va sortir 1,2,3

Exemple simple de script :

#!/bin/bash
DIR_TO_WATCH=${1}
COMMAND=${2}

watch -d -t -g ls -lR ${DIR_TO_WATCH} && ${COMMAND}

8 votes

Joli coup. J'ai testé et il semble avoir un problème lorsque la liste est longue et que le fichier modifié tombe en dehors de l'écran. Une petite modification pourrait être quelque chose comme ceci : watch -d -t -g "ls -lR tmp | sha1sum"

3 votes

Si vous surveillez votre solution toutes les secondes, elle fonctionnera toujours et n'exécutera MY_COMMAND que si un fichier change : watch -n1 "watch -d -t -g ls -lR && MY_COMMAND".

0 votes

Ma version de watch (sous Linux, watch from procps-ng 3.3.10 ) accepte les secondes flottantes pour son intervalle, d'où watch -n0.2 ... fera un sondage tous les cinquièmes de seconde. C'est une bonne chose pour les tests unitaires de moins d'une milliseconde.

SistemesEz.com

SystemesEZ est une communauté de sysadmins où vous pouvez résoudre vos problèmes et vos doutes. Vous pouvez consulter les questions des autres sysadmins, poser vos propres questions ou résoudre celles des autres.

Powered by:

X