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)

33voto

Denilson Sá Maia Points 11713

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 à taper, c'est un peu difficile de se souvenir de ce qu'il faut taper, exactement, et c'est un peu difficile d'annuler ses effets (il faut lancer la fonction :au! BufWritePost myfile.py ). En outre, cette solution bloque Vim jusqu'à ce que la commande ait fini de s'exécuter.

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

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

1 votes

Cela peut être très intéressant lorsqu'il est combiné avec entr (voir ci-dessous) - faites simplement en sorte que vim touche un fichier fictif que entr surveille, et laissez entr faire le reste en arrière-plan... ou bien tmux send-keys si vous vous trouvez dans un tel environnement :)

0 votes

Sympa ! Vous pouvez créer une macro pour votre .vimrc fichier

33voto

davidtbernal Points 444

Si vous avez npm installé, nodemon est probablement le moyen le plus simple de commencer, surtout sous OS X, qui ne dispose apparemment pas d'outils inotify. Il prend en charge l'exécution d'une commande lorsqu'un dossier change.

5 votes

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

7 votes

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

1 votes

J'aimerais avoir plus d'informations, mais osx a une méthode pour suivre les changements, les fsevents.

31voto

Jonathan Hartley Points 924

rerun2 ( sur github ) est un Bash script 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 'rerun' dans votre PATH, et invoquez-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).

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

  • Il utilise inotify, et est donc plus réactif que les sondages. Fabuleux pour exécuter des tests unitaires de l'ordre de la milliseconde, ou pour rendre les fichiers dot de Graphviz, à chaque fois que vous appuyez sur 'save'.
  • Comme il est très rapide, vous n'avez pas besoin de lui dire d'ignorer les grands sous-répertoires (comme node_modules) pour des raisons de performances.
  • Elle est super réactive, car elle n'appelle inotifywait qu'une seule fois, au démarrage, au lieu de l'exécuter, et de subir le coût élevé de l'établissement des montres, à chaque itération.
  • C'est juste 12 lignes de Bash
  • Parce que c'est Bash, il interprète les commandes que vous lui passez exactement comme si vous les aviez tapées à une invite Bash. (Vraisemblablement, c'est moins cool si vous utilisez un autre Shell).
  • Elle ne perd pas les événements qui se produisent pendant l'exécution de COMMAND, contrairement à la plupart des autres solutions inotify de cette page.
  • Au premier événement, il entre dans une "période morte" de 0,15 seconde, pendant laquelle les autres événements sont ignorés, avant que COMMAND ne soit exécutée exactement une fois. Cela permet d'éviter que la rafale d'événements provoquée par la danse création-écriture-déplacement que Vi ou Emacs effectue lors de la sauvegarde d'un tampon ne provoque de multiples exécutions laborieuses d'une suite de tests qui pourrait être lente. Tous les événements qui se produisent alors que COMMAND est en cours d'exécution ne sont pas ignorés - ils provoquent une deuxième période morte et une nouvelle exécution.

Les choses que l'on pourrait détester à son sujet :

  • Il utilise inotify, donc ne fonctionnera pas en dehors de Linuxland.
  • Parce qu'il utilise inotify, il se dégonflera en essayant de surveiller des répertoires contenant plus de fichiers que le nombre maximum d'utilisateurs que inotify surveille. Par défaut, ce nombre semble être fixé à 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 les commandes contenant des alias Bash. Je pourrais jurer que cela fonctionnait auparavant. En principe, comme il s'agit de Bash et non de l'exécution de COMMAND dans un sous-shell, je m'attendrais à ce que cela fonctionne. J'aimerais savoir si quelqu'un sait pourquoi cela ne fonctionne pas. La plupart 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 il est exécuté pour provoquer manuellement une exécution supplémentaire de COMMAND. Pourrais-je ajouter cela d'une manière ou d'une autre, simplement ? Une boucle 'while read -n1' qui s'exécute simultanément et qui appelle aussi execute ?
  • Pour l'instant, je l'ai codé pour vider le terminal et imprimer la COMMANDE exécutée à chaque itération. Certaines personnes pourraient souhaiter ajouter des drapeaux en ligne de commande pour désactiver ce genre de choses, etc. Mais cela multiplierait la taille et la complexité de l'application.

Il s'agit d'un raffinement de la réponse de @cychoi.

2 votes

Je crois que vous devriez utiliser "$@" au lieu de $@ afin de fonctionner correctement avec des arguments contenant des espaces. Mais en même temps, vous utilisez eval ce qui oblige l'utilisateur de rerun à faire très attention aux citations.

0 votes

Merci Denilson. Pourriez-vous donner un exemple où la citation doit être faite avec précaution ? Je l'utilise depuis 24 heures et je n'ai pas vu de problèmes avec les espaces jusqu'à présent, ni avec les guillemets. soigneusement cité quoi que ce soit - juste invoqué comme rerun 'command' . Voulez-vous dire que si j'utilisais "$@", l'utilisateur pourrait invoquer comme rerun command (sans guillemets ?) Cela ne me semble pas aussi utile : Je ne veux généralement pas que Bash fasse tout traitement de la commande avant de la passer à la réexécution. Par exemple, si la commande contient "echo $myvar", alors je veux voir les nouvelles valeurs de myvar à chaque itération.

1 votes

Quelque chose comme rerun foo "Some File" pourrait se briser. Mais puisque vous utilisez eval il peut être réécrit comme suit rerun 'foo "Some File" . Notez que l'expansion du chemin peut parfois introduire des espaces : rerun touch *.foo se brisera probablement, et l'utilisation de rerun 'touch *.foo' a une sémantique légèrement différente (l'expansion du chemin ne se produit qu'une fois, ou plusieurs fois).

19voto

Jay Points 281

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

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

Dans mon cas, je modifie le html localement et je 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"

15voto

MikeyMike Points 159

Voici un simple Shell Bourne Shell Shell 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 que le fichier que vous surveillez est plus récent que la copie
  4. S'il est plus récent, il écrase la copie avec l'original plus récent 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 sous 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 juste coder en dur le nom du fichier temporaire.

9 votes

L'interrogation 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 faites $cmd mais, heureusement, cela peut être facilement corrigé : il suffit d'abandonner l'option cmd et exécuter "$@" . Votre script n'est pas adapté à la surveillance d'un grand fichier, mais cela pourrait être corrigé en remplaçant cp por touch -r (vous n'avez besoin que de la date, pas du contenu). Du point de vue de la portabilité, le -nt Le test nécessite bash, ksh ou zsh.

1 votes

Je suis curieux de savoir comment cela se compare avec superuser.com/a/634313 Est-ce que créer une copie est plus portable que de simplement stat sur le dossier ?

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