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)

1voto

LondonRob Points 406

Pour les personnes qui trouvent cette information en cherchant sur Google les modifications apportées à une particulier la réponse est beaucoup plus simple (inspirée de La réponse de Gilles ).

Si vous voulez faire quelque chose nach un fichier particulier a été écrit, 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

Sauvegardez ceci comme, par exemple, copy_myfile.sh et mettre le .sh dans le fichier /etc/init.d/ pour qu'il soit exécuté au démarrage.

0 votes

Partage le problème avec la réponse de Giles qui exécute inotifywait à chaque itération, ce qui peut être peu réactif pour regarder récursivement de très grands répertoires. Voir la réponse de cychoi pour la correction de ce problème.

1voto

William Entriken Points 2154

Utilisation de base

Voici une solution qui ne nécessite pas l'installation d'un logiciel supplémentaire et qui fonctionne d'emblée.

tail -q --follow=name myfile.txt | head -n 0

Cette commande se termine dans les conditions suivantes :

  • Une ligne est ajoutée à myfile.txt après l'exécution de la commande
  • El myfile.txt est remplacé par un autre après l'exécution de la commande

Vous dites que vous utilisez vim, et que vim remplacera le fichier lors de l'enregistrement. J'ai testé que cela fonctionne avec vim.

Vous pouvez ignorer la sortie de cette commande, elle peut mentionner quelque chose comme :

queue : 'monfichier.txt' a été remplacé ; suit la fin du nouveau fichier

Utilisation avancée

Vous pouvez combiner cela avec timeout pour renvoyer vrai ou faux. Vous pouvez l'utiliser comme ceci :

timeout 5s bash -c 'tail -q --follow=name pipe 2> /dev/null | head -n 0' && echo changed || echo timeout

Discussion

tail utilise inotify sous le capot. C'est ainsi que vous obtenez ce comportement asynchrone fantaisiste sans aucune interrogation. Il y a probablement un autre programme Unix standard qui utilise inotify dont nous pouvons abuser de manière plus élégante.

Parfois, ces commandes se terminent tout de suite, mais si vous les exécutez immédiatement une deuxième fois, elles fonctionnent comme prévu. J'ai fait une erreur à un endroit, s'il vous plaît aidez-moi à corriger cela.

Sur RHEL, je peux utiliser :

timeout 5s sh -c 'gio monitor pipe | head -n 0' && echo changed || echo timeout

Mais je ne suis pas sûr que ce soit portable.

1voto

leondepeon Points 134

Si vous ne voulez pas installer quelque chose de nouveau pour cela, voici un petit Shell-Shell que vous pouvez mettre dans votre chemin (par exemple sous $HOME/bin ). Il exécute une commande lorsque le ou les fichiers fournis sont modifiés. Par exemple :

$ onchange './build' *.txt

#!/bin/sh
cmd="$1"; shift
files="$@"
changed() { tar -c $files | md5sum; } # for on every save use: `stat -c %Z $files`
while true; do
  if [ "$(changed)" != "$last" ]; then
    last="$(changed)"
    $cmd
  fi
  sleep 1
done

Il déchire, puis hachouille le contenu des fichiers et/ou des répertoires, de sorte qu'il ne s'exécute pas chaque fois que vous appuyez compulsivement sur CTRL-S (ou tapez :w ), mais seulement lorsque quelque chose change réellement. Notez qu'il vérifiera toutes les secondes, donc n'en incluez pas trop ou votre machine pourrait devenir lente. Si vous voulez qu'il s'exécute à chaque sauvegarde, utilisez stat en lieu et place (voir commentaire). En outre, pour mac md5sum s'appelle md5 si je me souviens bien.

Petite astuce : au moment où vous voulez l'utiliser, vous voudrez probablement répéter la dernière commande que vous venez d'exécuter, mais encore et encore. Vous pouvez utiliser la commande !! raccourci pour "injecter" la dernière commande dans celle-ci :

$ onchange "!!" *.txt

1voto

Farzad Khalafi Points 11

Pour ceux qui utilisent docker, aucune de ces solutions ne fonctionnerait car les fichiers modifiés du côté hôte ne déclenchent pas de changements de fichiers à l'intérieur des conteneurs docker. ma solution était un simple script :

#!/bin/bash

command="ls -al *"
out=$($command | md5sum)
echo $out
exit
last=$($command | md5sum)
while true
do
    if [ "$out" != "$last" ]
    then
        #command to run when change is detected

        touch src/index.js
        out=$($command | md5sum)
        echo "change detected"
    fi
    sleep 5
    last=$($command | md5sum)
done

J'ai fait ce script pour pouvoir développer pour nodejs/react en utilisant nodemon pour redémarrer le serveur nodejs si nécessaire.

1voto

Dethcrow Points 63

Le gardien de Facebook a construit une certaine communauté. Vous utilisez peut-être déjà un outil qui en a besoin.

Il détecte les nouveaux fichiers correspondant à des modèles donnés. Pour les grands projets, contrairement à entr Je n'ai pas besoin de modifier les limites des fichiers système et de redémarrer l'ordinateur. Le glob suivant fonctionne simplement, contre tous les projets, par exemple les fichiers Python.

watchman-make --pattern '**/*.py' --run './myfile.py'

Il attend qu'un fichier soit modifié avant de le reconstruire. Vous pouvez vouloir exécuter la commande une fois sans changement, puis commencez à regarder.

(export CMD="$SHELL -c './myfile.py'" && eval "$CMD"; watchman-make --pattern '**/*.py' --run "$CMD")

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