Question Comment exécuter une commande chaque fois qu'un fichier change?


Je veux un moyen simple et rapide d'exécuter une commande chaque fois qu'un fichier change. Je veux quelque chose de très simple, quelque chose que je vais laisser courir sur un terminal et le fermer chaque fois que je finis de travailler avec ce fichier.

Actuellement, j'utilise ceci:

while read; do ./myfile.py ; done

Et puis je dois aller à ce terminal et appuyer sur Entrer, 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 genre de solution que je veux maintenant.

Mettre à jour: Je veux quelque chose de simple, jetable si possible. De plus, je veux que quelque chose fonctionne 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 ne dois en accepter qu'un, j'accepte celui que j'ai effectivement utilisé (c'était simple, rapide et facile à retenir), même si je sais que ce n'est pas le plus élégant.


357
2017-08-27 20:02


origine


Possible duplication de sites croisés de: stackoverflow.com/questions/2972765/... (bien que ce soit ici sur le sujet =)) - Ciro Santilli 新疆改造中心 六四事件 法轮功
J'ai référencé avant un doublon de site et il a été refusé: S;) - Francisco Tapia
La solution de Jonathan Hartley s'appuie sur d'autres solutions ici et corrige de gros problèmes que les réponses les plus votées ont: 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 à github.com/tartley/rerun2 (ou à une autre solution sans ces défauts) - nealmcb


Réponses:


Simple, en utilisant inotifywait (installez vos distributions 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 de code est plus simple, mais il présente un inconvénient majeur: les modifications apportées inotifywait ne fonctionne pas (en particulier pendant myfile est en cours d'exécution). Le second extrait n'a pas ce défaut. Cependant, prenez garde qu'il suppose que le nom du fichier ne contient pas d'espaces. Si cela pose problème, utilisez le --format option pour modifier la sortie pour ne pas inclure le nom du fichier:

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

De toute façon, il y a une limitation: si certains programmes remplacent myfile.py avec un fichier différent, plutôt que d'écrire à l'existant myfile, inotifywait mourront. De nombreux é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 utilisant 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 Unix, en utilisant l’analogue de chaque variante de Linux).


341
2017-08-27 20:54



J'ai encapsulé tout cela (avec quelques astuces basiques) dans un format simple à utiliser. sleep_until_modified.sh script, disponible à: bitbucket.org/denilsonsa/small_scripts/src - Denilson Sá Maia
while sleep_until_modified.sh derivation.tex ; do latexmk -pdf derivation.tex ; done est fantastique. Je vous remercie. - Rhys Ulerich
inotifywait -e delete_self semble bien fonctionner pour moi. - Kos
C'est simple mais il y a deux problèmes importants: les événements peuvent être manqués (tous les événements dans la boucle) et l'initialisation de inotifywait se fait à chaque fois, ce qui rend cette solution plus lente pour les grands dossiers récursifs. - Wernight
Pour certaines raisons while inotifywait -e close_write myfile.py; do ./myfile.py; done quitte 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


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

Il est très facile de spécifier plusieurs fichiers à regarder (limité uniquement par ulimit -n), élimine les problèmes liés au remplacement des fichiers et nécessite moins de syntaxe bash:

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

Je l'ai utilisé sur toute mon arborescence de projet pour exécuter les tests unitaires du code que je suis en train de modifier, et cela a déjà été un énorme coup de pouce pour mon flux de travail.

Des drapeaux comme -c (effacer l'écran entre les exécutions) et -d (quitter lorsqu'un nouveau fichier est ajouté à un répertoire surveillé) ajoute encore plus de flexibilité, par exemple:

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

Au début de 2018, il est encore en développement actif et peut être trouvé dans Debian et Ubuntu (apt install entr); construire à partir du repo de l'auteur était sans douleur dans tous les cas.


120
2017-10-25 09:41



Ne gère pas les nouveaux fichiers et leurs modifications. - Wernight
@Wernight - depuis le 7 mai 2014, entr a le nouveau -d drapeau; c'est un peu plus long, mais vous pouvez le faire while sleep 1 ; do find . -name '*.py' | entr -d ./myfile.py ; done traiter de nouveaux fichiers. - Paul Fenney
disponible dans aur aur.archlinux.org/packages/entr - Victor Häggqvist
le meilleur que j'ai trouvé sur l'OS X à coup sûr. fswatch saisit trop d'événements funky et je ne veux pas passer le temps à comprendre pourquoi - dtc
Il est à noter que entr est disponible sur Homebrew, donc brew install entr travaillera comme prévu - jmarceli


J'ai écrit un programme Python pour faire exactement cela appelé quand-changé.

L'utilisation est simple:

when-changed FILE COMMAND...

Ou regarder plusieurs fichiers:

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

FILE peut être un répertoire. Regarder récursivement avec -r. Utilisation %f transmettre le nom de fichier à la commande.


100
2018-06-30 13:34



@ yisangkok oui, dans la dernière version du code :) - joh
Maintenant disponible à partir de "pip installation when-changed". Fonctionne toujours bien. Merci. - A. L. Flanagan
Pour effacer l'écran en premier, vous pouvez utiliser when-changed FILE 'clear; COMMAND'. - Dave James Miller
Cette réponse est tellement meilleure que je peux le faire sous Windows également. Et ce gars a en fait écrit un programme pour obtenir la réponse. - Wolfpack'08
Bonnes nouvelles tout le monde! when-changed est maintenant multi-plateforme! Découvrez les dernières 0.3.0 version :) - joh


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

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

45
2017-08-20 17:12



Ne serait pas stat-sur l'heure modifiée être une meilleure "chaque fois qu'un fichier change" répondez? - Xen2050
Est-ce que l'exécution de statistiques plusieurs fois par seconde entraînerait 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 «montre grognée» pour compiler mon code c chaque fois que j'apporte des modifications - Oskenso Kashi
C'est bien si vous connaissez le nom du fichier à surveiller à l'avance. Mieux serait de passer le nom de fichier au script. Mieux encore serait si vous pouviez passer beaucoup de noms de fichiers (par exemple "mywatch * .py"). Mieux encore, il pourrait fonctionner de manière récursive sur des fichiers dans des sous-répertoires, ce que font d’autres solutions. - Jonathan Hartley
Juste au cas où quelqu'un s'interrogerait sur les lectures lourdes, j'ai testé ce script dans Ubuntu 17.04 avec un sommeil de 0.05s et vmstat -d surveiller l'accès au disque. Il semble que Linux fasse un travail fantastique pour mettre en cache ce genre de chose: D - Oskenso Kashi
Il y a une faute de frappe dans "COMMAND", j'essayais de corriger, mais S.O. dit "Modifier ne devrait pas être moins de 6 caractères" - user337085


Solution utilisant Vim:

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

Mais je ne veux pas que cette solution soit un peu ennuyeuse à taper, il est un peu difficile de se rappeler exactement ce qu’il faut taper, et il est un peu difficile de défaire ses effets (il faut exécuter :au! BufWritePost myfile.py). De plus, cette solution bloque Vim jusqu'à ce que la commande soit terminée.

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

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


28
2017-08-27 20:12



Cela peut être très agréable en combinaison avec entr (voir ci-dessous) - il suffit de faire en sorte que vim touche un fichier factice surveillé par entr et laisse entr faire le reste en arrière-plan ... ou tmux send-keys si vous êtes dans un tel environnement :) - Paul Fenney
agréable! vous pouvez faire une macro pour votre .vimrc fichier - ErichBSchulz


S'il vous arrive d'avoir npm installée, nodemon est probablement la manière la plus simple de démarrer, en particulier sous OS X, qui n’a apparemment pas d’outil d’information. Il prend en charge l'exécution d'une commande lorsqu'un dossier change.


24
2018-06-09 23:51



Cependant, il ne surveille que les fichiers .js et .coffee. - zelk
La version actuelle semble prendre en charge n'importe quelle commande, par exemple: nodemon -x "bundle exec rspec" spec/models/model_spec.rb -w app/models -w spec/models - kek
Je voudrais avoir plus d'informations, mais osx a une méthode pour suivre les changements, fsevents - ConstantineK
Sur OS X, vous pouvez également utiliser Lancer les démons avec un WatchPaths clé comme indiqué dans mon lien. - Adam Johns


Voici un script shell shell Bourne 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 plus récent et exécute la commande
  5. Nettoie après que 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 le nom du fichier temporaire.


12
2017-08-27 21:23



L'interrogation est la seule méthode 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 cotation quand vous le faites $cmd, mais heureusement, c'est facilement réparable: cmd variable et exécuter "$@". Votre script ne convient pas pour surveiller un fichier volumineux, mais cela pourrait être résolu en remplaçant cp par 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


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 github en tant que "réexécuter" sur votre PATH et appelez-la en utilisant:

rerun COMMAND

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

Les choses que l'on pourrait aimer à ce sujet:

  • Il utilise inotify, il est donc plus réactif que le polling. Fabuleux pour exécuter des tests unitaires de moins d'une milliseconde ou pour rendre des fichiers de points Graphviz, chaque fois que vous appuyez sur «enregistrer».
  • Comme il est si rapide, vous n'avez pas à vous soucier de lui ignorer les grands sous-répertoires (comme node_modules), uniquement pour des raisons de performances.
  • Il est super réactif, car il appelle uniquement inotifywait une fois, au démarrage, au lieu de l'exécuter, et encourt le coût élevé de l'établissement de montres, à chaque itération.
  • Ce n'est que 12 lignes de Bash
  • Parce que c'est Bash, il interprète les commandes que vous lui transmettez exactement comme si vous les aviez tapées à une invite 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 la commande, contrairement à la plupart des autres solutions inotify sur cette page.
  • Lors du premier événement, il entre une «période morte» de 0,15 seconde, pendant laquelle les autres événements sont ignorés, avant que COMMAND ne soit exécuté exactement une fois. C'est pour cette raison que le flot d'événements provoqués par la danse create-write-move que fait Vi ou Emacs lors de la sauvegarde d'un tampon n'entraîne pas plusieurs exécutions laborieuses d'une suite de tests qui peut être lente. Tous les événements qui surviennent alors que la commande s'exécute ne sont pas ignorés - ils provoqueront une deuxième période morte et une exécution ultérieure.

Les choses que l'on pourrait ne pas aimer à ce sujet:

  • Il utilise inotify, donc ne fonctionnera pas en dehors de Linuxland.
  • Comme il utilise inotify, il ne tentera pas de regarder des répertoires contenant plus de fichiers que le nombre maximum de montres inotify par l'utilisateur. Par défaut, cela semble être d'environ 5 000 à 8 000 sur différentes machines que j'utilise, mais il est facile à 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, comme il s'agit de Bash, sans exécuter COMMAND dans un sous-shell, je pense que cela fonctionnera. J'aimerais entendre Si quelqu'un sait pourquoi il ne le fait pas. Beaucoup des autres solutions sur cette page ne peuvent pas non plus exécuter de telles commandes.
  • Personnellement, j'aurais aimé pouvoir taper sur une touche du terminal pour exécuter manuellement une commande supplémentaire. Puis-je ajouter cela d'une manière ou d'une autre, simplement? Une boucle 'while read -n1' en cours d'exécution simultanée, que les appels exécutent également?
  • En ce moment, je l'ai codé pour effacer le terminal et imprimer la commande exécutée à chaque itération. Certaines personnes voudront peut-être ajouter des drapeaux en ligne de commande pour désactiver cette fonctionnalité, etc. Mais cela augmentera la taille et la complexité.

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


12
2017-09-09 21:49



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 rerun à être extrêmement prudent lors de la citation. - Denilson Sá Maia
Merci Denilson. Pourriez-vous donner un exemple où le devis doit être fait avec soin? Je l’utilise depuis les dernières 24 heures et je n’ai vu aucun problème soigneusement cité n'importe quoi - juste invoqué comme rerun 'command'. Êtes-vous juste en train de dire que si j'utilisais "$ @", alors l'utilisateur pourrait invoquer comme rerun command (sans guillemets?) Cela ne me semble pas utile: je ne veux généralement pas que Bash fasse tout traitement de commande avant de le passer à réexécuter. par exemple. Si la commande contient "echo $ myvar", alors je veux voir les nouvelles valeurs de myvar dans chaque itération. - Jonathan Hartley
Quelque chose comme rerun foo "Some File" pourrait casser Mais depuis que vous utilisez eval, il peut être réécrit comme rerun 'foo "Some File". Notez que, parfois, l'extension du chemin peut introduire des espaces: rerun touch *.foo va probablement casser, et en utilisant rerun 'touch *.foo' La sémantique est légèrement différente (l'extension du chemin ne se produit qu'une seule fois ou plusieurs fois). - Denilson Sá Maia
Merci pour l'aide. Oui: rerun ls "some file" se casse à cause des espaces. rerun touch *.foo* fonctionne normalement, mais échoue si les noms de fichiers qui correspondent à * .foo contiennent des espaces. Merci de m'aider à voir comment rerun 'touch *.foo' a une sémantique différente, mais je pense que la version avec des guillemets simples est la sémantique que je veux: je veux que chaque itération de réexécution agisse comme si je tapais à nouveau la commande - donc je vouloir  *.foo à développer à 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 d'autres. - Jonathan Hartley