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


433

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 en cours d'exécution sur un terminal et que je fermerai dès que j'aurai fini de travailler avec ce fichier.

Actuellement, j'utilise ceci:

while read; do ./myfile.py ; done

Et puis je dois aller sur ce terminal et appuyer sur Enter, chaque fois que je sauvegarde 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 type de solution que je souhaite maintenant.

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

À propos des réponses: Merci pour toutes vos réponses! Tous sont très bons et chacun adopte une approche très différente des autres. Comme je n'ai besoin que d'un seul, j'accepte celui que j'ai utilisé (c'était simple, rapide et facile à retenir), même si je sais que ce n'est pas le plus élégant.


Possible double cross site de: stackoverflow.com/questions/2972765/... (bien qu'ici il est sur le sujet =))
Ciro Santilli新疆改造中心法轮功六四事件

J'ai déjà référencé avant un doublon de site croisé et il a été refusé: S;)
Francisco Tapia

4
La solution de Jonathan Hartley s’appuie sur d’autres solutions et résout les gros problèmes auxquels se heurtent les réponses les plus votées: manquer certaines modifications et être inefficace. S'il vous plaît changer la réponse acceptée à la sienne, qui est également maintenue sur github à l' adresse github.com/tartley/rerun2 (ou à une autre solution sans ces défauts)
nealmcb

Réponses:


404

Simple, en utilisant inotifywait (installez le inotify-toolspaquet de votre distribution ):

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 comporte un inconvénient majeur: il manquera les modifications effectuées tant qu'il inotifywaitn'est pas en cours d'exécution (en particulier lorsqu'il myfileest en cours d'exécution). Le deuxième extrait n'a pas ce défaut. Attention toutefois, cela suppose que le nom du fichier ne contient pas d'espaces. Si cela pose un problème, utilisez l' --formatoption pour modifier la sortie afin de ne pas inclure 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 existe une limite: si un programme est remplacé myfile.pypar un fichier différent, plutôt que d'écrire dans le fichier existant myfile, il inotifywaitva disparaître. Beaucoup d'éditeurs travaillent de cette façon.

Pour surmonter cette limitation, utilisez inotifywaitsur 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 les mêmes fonctionnalités sous-jacentes, telles que 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 inotify de Linux de chaque variante).


46
J'ai résumé tout cela (avec quelques astuces assez simples) dans un sleep_until_modified.shscript simple à utiliser , disponible à l' adresse
Denilson Sá Maia

14
while sleep_until_modified.sh derivation.tex ; do latexmk -pdf derivation.tex ; doneest fantastique. Je vous remercie.
Rhys Ulerich

5
inotifywait -e delete_selfsemble bien fonctionner pour moi.
Kos

3
C'est simple, mais comporte deux problèmes importants: des événements peuvent être manqués (tous les événements de la boucle) et l'initialisation d'inotifywait est effectuée à chaque fois, ce qui ralentit la solution pour les dossiers récursifs volumineux.
mercredi

6
Pour une raison quelconque, while inotifywait -e close_write myfile.py; do ./myfile.py; donequitte toujours sans exécuter la commande (bash et zsh). Pour que cela fonctionne, je devais ajouter || true, par exemple: while inotifywait -e close_write myfile.py || true; do ./myfile.py; done
ideasman42

165

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

Il est très facile de spécifier plusieurs fichiers à regarder (limité uniquement par ulimit -n), simplifie le traitement des fichiers à remplacer et nécessite moins de syntaxe bash:

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

Je l'utilise sur l'ensemble de l'arborescence source de mon projet pour exécuter les tests unitaires du code que je suis en train de modifier, ce qui a déjà considérablement dynamisé mon flux de travail.

Les indicateurs tels que -c(effacer l'écran entre les exécutions) et -d(quitter lorsqu'un nouveau fichier est ajouté à un répertoire surveillé) ajoutent encore plus de flexibilité. Vous pouvez par exemple:

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

Début 2018, il est toujours en développement actif et peut être trouvé dans Debian & Ubuntu ( apt install entr); la construction du dépôt de l'auteur était sans douleur dans tous les cas.


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

2
@Wernight - à compter du 7 mai 2014, entr a le nouveau -ddrapeau; c'est un peu plus long, mais vous pouvez faire while sleep 1 ; do find . -name '*.py' | entr -d ./myfile.py ; donepour traiter de nouveaux fichiers.
Paul Fenney

1
entr est également disponible dans les dépôts debian au moins à partir de debian jessie / 8.2 sur ...
Peter V. Mørch

5
Le meilleur que j'ai trouvé sur OS X à coup sûr. fswatch attrape trop d’événements géniaux et je ne veux pas passer le temps de comprendre pourquoi
dtc

5
Il est à noter que entr est disponible sur Homebrew, donc brew install entrfonctionnera comme prévu
jmarceli

108

J'ai écrit un programme Python pour faire exactement ce qu'on appelle quand-changé .

L'utilisation est simple:

when-changed FILE COMMAND...

Ou pour regarder plusieurs fichiers:

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

FILEpeut être un répertoire. Regarder récursivement avec -r. Utilisez %fpour passer le nom de fichier à la commande.


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

4
Maintenant disponible à partir de "pip install when-modified". Fonctionne toujours bien. Merci.
AL Flanagan

2
Pour effacer l'écran en premier, vous pouvez utiliser when-changed FILE 'clear; COMMAND'.
Dave James Miller

1
Cette réponse est bien meilleure car je peux le faire sous Windows également. Et ce gars a en fait écrit un programme pour obtenir la réponse.
Wolfpack'08

4
Bonnes nouvelles tout le monde! when-changedest maintenant multi-plateforme! Découvrez la dernière version 0.3.0 :)
joh

52

Que diriez-vous de ce script? Il utilise la statcommande pour obtenir le temps d'accès d'un fichier et exécute une commande chaque fois que le temps d'accès est modifié (chaque fois qu'un fichier est utilisé).

#!/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

2
Est-ce que statle temps modifié ne serait pas une meilleure réponse "à chaque changement de fichier"?
Xen2050

1
L'exécution de stat plusieurs fois par seconde entraînerait-elle de nombreuses lectures sur le disque? ou l’appel système fstat ferait-il automatiquement en cache ces réponses d’une manière ou d’une autre? J'essaie d'écrire une sorte de "montre grognante" pour compiler mon code c chaque fois que je fais des modifications
Oskenso Kashi

Cela est utile si vous connaissez le nom du fichier à surveiller au préalable. Mieux serait de passer le nom du fichier au script. Mieux encore serait si vous pouviez passer plusieurs noms de fichiers (par exemple, "mywatch * .py"). Cela serait encore mieux si elle pouvait fonctionner de manière récursive sur des fichiers dans des sous-répertoires également, comme le font certaines des autres solutions.
Jonathan Hartley

5
Juste au cas où quelqu'un s'interrogerait sur les lectures lourdes, j'ai testé ce script dans Ubuntu 17.04 avec une veille de 0.05s et vmstat -den surveillant l'accès au disque. Il semble que linux fasse un travail fantastique pour mettre ce genre de choses en cache: D
Oskenso Kashi

Il y a une faute de frappe dans "COMMAND", j'essayais de corriger, mais SO dit: "Edit ne doit pas être inférieur à 6 caractères"
user337085

30

Solution utilisant Vim:

:au BufWritePost myfile.py :silent !./myfile.py

Mais je ne veux pas de cette solution parce que c’est un peu ennuyeux de taper, c’est un peu difficile de se rappeler quoi taper, exactement, et c’est un peu difficile d’annuler ses effets (nécessité de fonctionner :au! BufWritePost myfile.py). De plus, cette solution bloque Vim jusqu'à l'exécution complète de la commande.

J'ai ajouté cette solution ici juste pour être complet, car cela pourrait aider d'autres personnes.

Pour afficher la sortie du programme (et perturber complètement votre flux d'édition, car la sortie écrira sur votre éditeur pendant quelques secondes, jusqu'à ce que vous appuyiez sur Entrée), supprimez la :silentcommande.


1
Cela peut être très agréable quand on le combine entr(voir ci-dessous) - il suffit de faire en sorte que vim touche un fichier factice surveillé par entr, et laissez-le faire le reste en arrière-plan ... ou tmux send-keyssi vous vous trouvez dans un tel environnement :)
Paul Fenney

agréable! vous pouvez créer une macro pour votre .vimrcfichier
ErichBSchulz

23

Si vous vous êtes npminstallé, nodemonc’est probablement le moyen le plus simple de commencer, en particulier sous OS X, qui ne dispose apparemment pas d’outils inotify. Il prend en charge l'exécution d'une commande lorsqu'un dossier est modifié.


5
Cependant, il ne surveille que les fichiers .js et .coffee.
Zelk

6
La version actuelle semble supporter n'importe quelle commande, par exemple: nodemon -x "bundle exec rspec" spec/models/model_spec.rb -w app/models -w spec/models
kek

1
Je voudrais avoir plus d'informations, mais osx a une méthode pour suivre les changements, févents
ConstantineK

1
Sous OS X, vous pouvez également utiliser les démons de lancement avec une WatchPathsclé, comme indiqué dans mon lien.
Adam Johns

18

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

watch -d -t -g ls -lR

Cette commande se fermera quand la sortie changera, ls -lRlistera tous les fichiers et répertoires avec leur taille et leurs dates, donc si un fichier est modifié, il devrait quitter la commande, comme le dit l'homme:

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

Je sais que personne ne lira cette réponse, mais j'espère que quelqu'un y parviendra.

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 de script simple:

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

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

4
Nice bidouille. J'ai testé et il semble y avoir un problème lorsque la liste est longue et que le fichier modifié est en dehors de l'écran. Une petite modification pourrait ressembler à ceci: watch -d -t -g "ls -lR tmp | sha1sum"
Atle

3
si vous regardez votre solution à chaque seconde, il fonctionne toujours et exécuter ma_commande que si quelques modifications du fichier: regarder -n1 « regarder -d -t -g ls -lR && ma_commande »
mnesarco

Ma version de watch (Sous Linux, watch from procps-ng 3.3.10) accepte des secondes flottantes pour son intervalle; elle watch -n0.2 ...sera donc interrogée tous les cinq secondes. Bon pour les tests unitaires sains inférieurs à la milliseconde.
Jonathan Hartley

15

rerun2( sur github ) est un script Bash de 10 lignes de la forme:

#!/usr/bin/env bash

function execute() {
    clear
    echo "$@"
    eval "$@"
}

execute "$@"

inotifywait --quiet --recursive --monitor --event modify --format "%w%f" . \
| while read change; do
    execute "$@"
done

Enregistrez la version de github en tant que "réexécutez" sur votre PATH et appelez-la en utilisant:

rerun COMMAND

Il exécute COMMAND chaque fois qu'il y a un événement de modification du système de fichiers dans votre répertoire actuel (récursif.)

Des choses qu'on pourrait aimer à ce sujet:

  • Il utilise inotify, il est donc plus réactif que polling. Fabuleux pour l'exécution de tests unitaires inférieurs à une milliseconde ou le rendu de fichiers de points graphviz chaque fois que vous cliquez sur "Enregistrer".
  • Parce que c'est si rapide, vous n'avez pas besoin de lui dire d'ignorer les grands sous-répertoires (tels que node_modules) uniquement pour des raisons de performances.
  • Il est extrêmement super réactif, car il appelle inotifywait une seule fois, au démarrage, au lieu de l’exécuter, et entraîne le coût onéreux de l’établissement de montres, à chaque itération.
  • C'est juste 12 lignes de Bash
  • Parce que c'est Bash, il interprète les commandes que vous transmettez exactement comme si vous les aviez tapées à l'invite de Bash. (Vraisemblablement, c'est moins cool si vous utilisez un autre shell.)
  • Il ne perd pas les événements qui se produisent pendant l'exécution de COMMAND, contrairement à la plupart des autres solutions inotify présentées sur cette page.
  • Lors du premier événement, il entre dans une "période morte" de 0,15 seconde, au cours de laquelle les autres événements sont ignorés, avant que COMMAND ne soit exécuté exactement une fois. Ainsi, la multitude d'événements provoqués par la danse create-write-move que Vi ou Emacs font lors de la sauvegarde d'un tampon ne provoque pas de multiples exécutions laborieuses d'une suite de tests potentiellement lente. Tous les événements qui se produisent pendant l'exécution de COMMAND ne sont pas ignorés - ils provoqueront une seconde période d'inactivité et une exécution ultérieure.

Des choses qu'on pourrait ne pas aimer à ce sujet:

  • Il utilise inotify, donc ne fonctionnera pas en dehors de Linuxland.
  • Puisqu'il utilise inotify, il sera difficile d'essayer de regarder les répertoires contenant plus de fichiers que le nombre maximal de surveillances d'utilisateur inotify. Par défaut, cela semble être réglé à environ 5 000 à 8 000 sur les différentes machines que j'utilise, mais il est facile de l'augmenter. Voir https://unix.stackexchange.com/questions/13751/kernel-inotify-watch-limit-reached
  • Il ne parvient pas à exécuter des commandes contenant des alias Bash. Je pourrais jurer que cela fonctionnait. En principe, étant donné qu'il s'agit de Bash et n'exécutant pas COMMAND dans un sous-shell, je m'attendrais à ce que cela fonctionne. J'adorerais entendre Si quelqu'un sait pourquoi. Beaucoup des autres solutions de cette page ne peuvent pas non plus exécuter de telles commandes.
  • Personnellement, j'aimerais pouvoir appuyer sur une touche du terminal dans lequel elle s'exécute pour provoquer manuellement une exécution supplémentaire de COMMAND. Puis-je ajouter ceci d'une manière ou d'une autre, simplement? Une boucle 'en cours de lecture -n1' simultanément exécutée qui appelle également execute?
  • À l'heure actuelle, je l'ai codé pour vider le terminal et imprimer la commande exécutée à chaque itération. Certaines personnes voudront peut-être ajouter des indicateurs de ligne de commande pour désactiver des éléments tels que celui-ci, etc. Mais cela augmenterait considérablement la taille et la complexité.

Ceci est un raffinement de la réponse de @ cychoi.


2
Je crois que vous devriez utiliser "$@"au lieu de $@, afin de travailler correctement avec des arguments contenant des espaces. Mais en même temps, vous utilisez eval, ce qui oblige l’utilisateur de la réexécution à redoubler de prudence lorsqu’il cite.
Denilson Sá Maia

Merci Denilson. Pourriez-vous donner un exemple où la citation doit être faite avec soin? Je l'utilise depuis 24 heures et je n'ai encore vu aucun problème d'espace, ni rien cité avec précaution - simplement appelé rerun 'command'. Est -ce que vous venez de dire que si je « $ @ », l'utilisateur pourrait invoquer comme rerun commandcela ne semble pas aussi utile pour moi (sans les guillemets?): Je ne veux pas généralement Bash faire tout traitement de commande avant de le transmettre à relancer. Par exemple, si la commande contient "echo $ myvar", je souhaiterai voir les nouvelles valeurs de myvar à chaque itération.
Jonathan Hartley

1
Quelque chose comme rerun foo "Some File"pourrait se briser. Mais puisque vous utilisez eval, il peut être réécrit comme rerun 'foo "Some File". Notez que parfois l’extension de chemin peut introduire des espaces: rerun touch *.foosera probablement brisée et que l’utilisation de la rerun 'touch *.foo'sémantique sera légèrement différente (l’extension de chemin ne se produit qu’une ou plusieurs fois).
Denilson Sá Maia

Merci pour l'aide. Yep: rerun ls "some file"pauses à cause des espaces. rerun touch *.foo*fonctionne normalement, mais échoue si les noms de fichiers correspondant à * .foo contiennent des espaces. Merci de m'aider à comprendre la rerun 'touch *.foo'différence de sémantique, mais je soupçonne que la version avec guillemets simples est la sémantique que je veux: je veux que chaque itération de la réexécution agisse comme si je tapais la commande à nouveau - par conséquent, je veux *.foo être développé à chaque itération. . Je vais essayer vos suggestions pour examiner leurs effets ...
Jonathan Hartley

Plus de discussion sur ce PR ( github.com/tartley/rerun2/pull/1 ) et autres.
Jonathan Hartley

12

Voici un script shell shell simple qui:

  1. Prend deux arguments: le fichier à surveiller et une commande (avec des arguments, si nécessaire)
  2. Copie le fichier que vous surveillez dans le répertoire / tmp
  3. Vérifie toutes les deux secondes pour voir si le fichier que vous surveillez est plus récent que la copie.
  4. S'il est plus récent, il remplace la copie par l'original et exécute la commande.
  5. Nettoie après lui-même lorsque vous appuyez sur Ctr-C

    #!/bin/sh  
    f=$1  
    shift  
    cmd=$*  
    tmpf="`mktemp /tmp/onchange.XXXXX`"  
    cp "$f" "$tmpf"  
    trap "rm $tmpf; exit 1" 2  
    while : ; do  
        if [ "$f" -nt "$tmpf" ]; then  
            cp "$f" "$tmpf"  
            $cmd  
        fi  
        sleep 2  
    done  
    

Cela fonctionne sur FreeBSD. Le seul problème de portabilité auquel je peux penser est si un autre Unix n’a pas la commande mktemp (1), mais dans ce cas, vous pouvez simplement coder en dur le nom du fichier temporaire.


9
La polling est le seul moyen portable, mais la plupart des systèmes ont un mécanisme de notification de changement de fichier (inotify sur Linux, kqueue sur FreeBSD, ...). Vous avez un grave problème de citation lorsque vous le faites $cmd, mais heureusement, cela est facilement corrigible: abandonner la cmdvariable et exécuter "$@". Votre script ne convient pas à la surveillance d'un fichier volumineux, mais cela pourrait être corrigé en le remplaçant cppar touch -r(vous avez seulement besoin de la date, pas du contenu). En ce qui concerne la portabilité, le -nttest nécessite bash, ksh ou zsh.
Gilles

8

Regardez incron . Il ressemble à cron, mais utilise des événements inotify au lieu de time.


Cela peut fonctionner, mais créer une entrée incron est un processus qui demande beaucoup de travail par rapport aux autres solutions présentées sur cette page.
Jonathan Hartley

6

Une autre solution avec NodeJs, fsmonitor :

  1. Installer

    sudo npm install -g fsmonitor
    
  2. À partir de la ligne de commande (exemple, journaux de surveillance et "détail" si un fichier journal est modifié)

    fsmonitor -s -p '+*.log' sh -c "clear; tail -q *.log"
    

Note de côté: tail -F -q *.logje pense que l'exemple pourrait être résolu .
Volker Siegel

C'était juste pour donner un exemple, tail -fn'est-ce pas clearle terminal?
Atika

6

Regardez dans Guard, en particulier avec ce plugin:

https://github.com/hawx/guard-shell

Vous pouvez le configurer pour surveiller n'importe quel nombre de modèles dans le répertoire de votre projet et exécuter des commandes lorsque des modifications sont apportées. Bonne chance, même s'il existe un plugin disponible pour ce que vous essayez de faire en premier lieu.


6

Si vous avez installé nodemon , alors vous pouvez faire ceci:

nodemon -w <watch directory> -x "<shell command>" -e ".html"

Dans mon cas, je modifie le code HTML localement et l'envoie à mon serveur distant lorsqu'un fichier est modifié.

nodemon -w <watch directory> -x "scp filename jaym@jay-remote.com:/var/www" -e ".html"

6

Sous Linux:

man watch

watch -n 2 your_command_to_run

Lance la commande toutes les 2 secondes.

Si votre commande prend plus de 2 secondes à exécuter, la surveillance attendra que l'opération soit terminée avant de recommencer.


C'est assez simple, même si c'est un peu un gaspillage, c'est facile pour les tâches de développement telles que la modification en direct de styles.
Xeoncross

2
Que se passe-t-il lorsque l'exécution de la commande prend plus de deux secondes?
trentethreeforty

@thirtythreeforty Une rapide expérience sur Ubuntu montre que watch attendra pendant deux secondes, quel que soit le temps nécessaire à l'exécution de la commande. FWIW, la période de sommeil peut être spécifiée avec '-n', jusqu'à un minimum de 0,1 seconde.
Jonathan Hartley

5

Watchdog est un projet Python, et peut être exactement ce que vous cherchez:

Plateformes supportées

  • Linux 2.6 (inotify)
  • Mac OS X (FSEvents, kqueue)
  • FreeBSD / BSD (kqueue)
  • Windows (ReadDirectoryChangesW avec ports d’achèvement d’E / S; threads de travail ReadDirectoryChangesW)
  • Indépendamment du système d'exploitation (interroger le disque pour obtenir des instantanés de répertoire et les comparer périodiquement; lent et non recommandé)

Je viens d'écrire un wrapper en ligne de commande pour cela watchdog_exec:

Exemple fonctionne

Sur l’événement fs impliquant des fichiers et des dossiers du répertoire en cours, exécutez la echo $src $dstcommande, à moins que l’événement fs ne soit modifié, puis exécutez la python $srccommande.

python -m watchdog_exec . --execute echo --modified python

En utilisant des arguments courts et en limitant l'exécution à des événements impliquant " main .py":

python -m watchdog_exec . -e echo -a echo -s __main__.py

EDIT: Je viens de trouver que Watchdog a un CLI officiel appelé watchmedo, alors vérifiez-le également.


4

Si votre programme génère une sorte de journal / sortie, vous pouvez créer un Makefile avec une règle pour ce journal / sortie qui dépend de votre script et faire quelque chose comme:

while true; do make -s my_target; sleep 1; done

Alternativement, vous pouvez créer une cible factice et faire en sorte que sa règle appelle à la fois votre script et touche la cible factice (tout en dépendant de votre script).


11
while sleep 1 ; do something ; doneest légèrement meilleur que while true ; do something ; sleep 1 ; done. Au moins, il s’arrête facilement en appuyant sur Ctrl + C.
Denilson Sá Maia

La suppression du sommeil peut-elle provoquer une boucle occupée (le processeur génère de la chaleur et nuit à la durée de vie de la batterie d'un ordinateur portable)?
Steven Lu

2
@StevenLu: non, le sommeil n'est pas une attente occupée. Le problème est que si le sommeil est dans le corps, Control-C le tuera et la boucle recommencera. L'utilisation de l'alimentation lors du démarrage de la boucle est insignifiante. Essayez vous-même dans un terminal. Vous devez tenir Control-C pour que cela fonctionne, si vous dormez dans le corps.
Janus Troelsen le

Droite. Je pense que je l'ai manquée et je n'ai pas vu que le sommeil est toujours présent en tant que condition de boucle. Ce petit tweak est vraiment génial.
Steven Lu

4

2
Il s'agit d'un script Bash de 200 lignes comportant de nombreuses fonctionnalités, qui interroge statles noms de fichiers donnés, s'exécute md5sumsur la sortie et réexécute la commande donnée si cette valeur est modifiée. Parce que c'est Bash, je suppose que cela exécute bien la commande donnée exactement comme si vous l'aviez tapée à l'invite de Bash. (En revanche, la plupart des solutions écrites dans d'autres langues ne réussiront pas à exécuter des commandes contenant, par exemple, des alias de shell tels que ll)
Jonathan Hartley

4

Amélioré sur la réponse de Gilles .

Cette version est exécutée inotifywaitune fois et surveille les événements (.eg:) modifypar la suite. Cela inotifywait n'a pas besoin d'être ré-exécuté à chaque événement rencontré.

C'est rapide et rapide! (même lors de la surveillance récursive d'un grand répertoire)

inotifywait --quiet --monitor --event modify FILE | while read; do
    # trim the trailing space from inotifywait output
    REPLY=${REPLY% }
    filename=${REPLY%% *}
    # do whatever you want with the $filename
done

C'est la meilleure réponse sur la page pour les utilisateurs exclusivement Linux. Remplacez le contenu de la boucle par 'execute $ @', et l'utilisateur pourrait appeler ce script en transmettant sa propre commande à exécuter. Il fonctionne même avec les commandes contenant des alias de shell si vous le sourcez, en utilisant quelque chose comme ". Scriptname COMMAND". Ceci trouvera toujours le scriptname sur le PATH.
Jonathan Hartley

Je pense que vous voulez dire "en lisant REPONSE"?
Jonathan Hartley

1
Merci pour la clarification. Merci pour sa mise en phase! J'aurais supprimé ces commentaires, mais bien sûr maintenant je ne le ferai pas.
Jonathan Hartley

3

Un peu plus sur le côté de la programmation, mais vous voulez quelque chose comme inotify . Il existe des implémentations dans de nombreuses langues, telles que jnotify et pyinotify .

Cette bibliothèque vous permet de surveiller des fichiers uniques ou des répertoires entiers et renvoie des événements lorsqu'une action est découverte. Les informations renvoyées incluent le nom du fichier, l'action (créer, modifier, renommer, supprimer) et le chemin du fichier, entre autres informations utiles.


3

Pour ceux d'entre vous qui recherchent une solution FreeBSD, voici le port:

/usr/ports/sysutils/wait_on

3

J'aime la simplicité de while inotifywait ...; do ...; donecependant il a deux problèmes:

  • Les modifications de fichiers intervenant pendant le do ...;seront manquées
  • Lent lors de l'utilisation en mode récursif

J'ai donc créé un script d'assistance qui utilise inotifywait sans ces limitations: inotifyexec

Je vous suggère de mettre ce script sur votre chemin, comme dans ~/bin/. L'utilisation est décrite en exécutant simplement la commande.

Exemple: inotifyexec "echo test" -r .


Mise à jour du script pour prendre en charge la correspondance de modèle regex.
Wernight

Les deux problèmes sont résolus en utilisant inotifywait en mode "--monitor". Voir la réponse de cychoi.
Jonathan Hartley

3

Solution améliorée de Sebastian avec la watchcommande:

watch_cmd.sh:

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

while true; do
  watch -d -g "${WATCH_COMMAND}"
  ${COMMAND}
  sleep 1     # to allow break script by Ctrl+c
done

Exemple d'appel:

watch_cmd.sh "ls -lR /etc/nginx | grep .conf$" "sudo service nginx reload"

Cela fonctionne mais soyez prudent: la watchcommande a des bugs connus (voir man): elle réagit aux changements uniquement dans VISIBLE dans les parties terminales de la -g CMDsortie.


2

Vous pouvez essayer le réflexe .

Reflex est un petit outil pour regarder un répertoire et réexécuter une commande lorsque certains fichiers changent. C'est idéal pour exécuter automatiquement les tâches de compilation / lint / test et pour recharger votre application lorsque le code change.

# Rerun make whenever a .c file changes
reflex -r '\.c$' make

Pouvez-vous citer / expliquer un peu l’outil? Ayez une rapide lecture de la façon de recommander un logiciel pour vous guider.
Bertieb

1

Une réponse unique que j'utilise pour suivre un changement de fichier:

$ while true ; do NX=`stat -c %Z file` ; [[ $BF != $NX ]] && date >> ~/tmp/fchg && BF=$NX || sleep 2 ; done

Vous n'avez pas besoin d'initialiser BF si vous savez que la première date est l'heure de début.

C'est simple et portable. Il existe une autre réponse basée sur la même stratégie en utilisant un script ici. Regardez aussi.


Utilisation: J'utilise ceci pour déboguer et garder un œil dessus ~/.kde/share/config/plasma-desktop-appletsrc; que pour une raison inconnue continue de perdre monSwitchTabsOnHover=false


1

J'utilise ce script pour le faire. J'utilise inotify en mode moniteur

#!/bin/bash
MONDIR=$(dirname $1)
ARQ=$(basename $1)

inotifywait -mr -e close_write $MONDIR | while read base event file 
do
  if (echo $file |grep -i "$ARQ") ; then
    $1
  fi
done

Enregistrez ceci sous runatwrite.sh

Usage: runatwrite.sh myfile.sh

il lancera myfile.sh à chaque écriture.


1

Pour ceux qui utilisent OS X, vous pouvez utiliser un LaunchAgent pour surveiller les modifications apportées à un chemin / fichier et faire quelque chose lorsque cela se produit. FYI - LaunchControl est une bonne application pour facilement créer, modifier / supprimer des démons / agents.

( exemple pris d'ici )

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC -//Apple Computer//DTD PLIST 1.0//EN
http://www.apple.com/DTDs/PropertyList-1.0.dtd>
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>test</string>
    <key>ProgramArguments</key>
    <array>
        <string>say</string>
        <string>yy</string>
    </array>
    <key>WatchPaths</key>
    <array>
        <string>~/Desktop/</string>
    </array>
</dict>
</plist>


0

Pour les personnes qui trouvent ceci en cherchant des modifications dans un fichier particulier sur Google , la réponse est beaucoup plus simple (inspirée par la réponse de Gilles ).

Si vous voulez faire quelque chose après l' écriture d'un fichier particulier, voici comment:

while true; do
  inotifywait -e modify /path/to/file
  # Do something *after* a write occurs, e.g. copy the file
  /bin/cp /path/to/file /new/path
done

Enregistrez-le sous, par exemple, copy_myfile.shet placez le .shfichier dans le /etc/init.d/dossier pour le lancer au démarrage.


Partage le problème avec la réponse de Giles selon laquelle il s'exécute inotifywait à chaque itération, ce qui peut ne pas répondre lorsque vous regardez de manière récursive de très grands annuaires. Voir la réponse de cychoi pour la solution à ce problème.
Jonathan Hartley


0

Comme quelques-uns l'ont fait, j'ai également écrit un outil de ligne de commande léger pour le faire. Il est entièrement documenté, testé et modulaire.

Watch-Do

Installation

Vous pouvez l'installer (si vous avez Python3 et pip) en utilisant:

pip3 install git+https://github.com/vimist/watch-do

Usage

Utilisez-le tout de suite en exécutant:

watch-do -w my_file -d 'echo %f changed'

Aperçu des fonctionnalités

  • Prise en charge du fichier globbing (utiliser -w '*.py'ou -w '**/*.py')
  • Exécuter plusieurs commandes lors d’un changement de fichier (il suffit de spécifier à -dnouveau le drapeau)
  • Tient à jour de manière dynamique la liste des fichiers à surveiller si l’utilisation de la suppression est utilisée ( -rpour l’activer)
  • Plusieurs façons de "regarder" un fichier:
    • Heure de modification (par défaut)
    • Hachage de fichier
    • Trivial pour implémenter le vôtre (c'est l' observateur de ModificationTime )
  • Conception modulaire. Si vous souhaitez que des commandes soient exécutées, lorsqu’un fichier est utilisé, il est simple d’écrire votre propre observateur (mécanisme qui détermine si les analyses doivent être exécutées).
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.