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 à ce terminal et appuyez 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, à jeter 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.
Simple, en utilisant inotifywait (installez le paquet inotify-tools
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: les modifications effectuées alors que inotifywait
ne fonctionne pas (en particulier lorsque myfile
est en cours d'exécution) seront omises. 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'option --format
pour modifier le résultat 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
Dans les deux cas, il existe une limitation: si un programme remplace myfile.py
par un fichier différent, plutôt que d'écrire dans la variable myfile
existante, inotifywait
mourra. Beaucoup d'éditeurs travaillent de cette façon.
Pour surmonter cette limitation, utilisez inotifywait
dans 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, tel 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 chaque option). la variante de l'inotify de Linux).
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
(effacez l'écran entre les exécutions) et -d
(quitte 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.
J'ai écrit un programme Python pour faire exactement ceci appelé quand-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. Regardez récursivement avec -r
. Utilisez %f
pour transmettre le nom de fichier à la commande.
Que diriez-vous de ce script? Il utilise la commande stat
pour obtenir l'heure d'accès à un fichier et exécute une commande chaque fois qu'il y a une modification de l'heure d'accès (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
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 (il est nécessaire d’exécuter :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 commande :silent
.
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:
Des choses qu'on pourrait ne pas aimer à ce sujet:
Ceci est un raffinement de la réponse de @ cychoi.
Pour ceux qui ne peuvent pas installer inotify-tools
comme moi, cela devrait être utile:
watch -d -t -g ls -lR
Cette commande se ferme lorsque la sortie change. ls -lR
liste tous les fichiers et répertoires avec leur taille et leurs dates. Par conséquent, si un fichier est modifié, il doit 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}
Voici un script simple Shell Bourne Shell qui:
Nettoie tout seul 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.
Regardez incron . Il ressemble à cron, mais utilise des événements inotify au lieu de time.
Une autre solution avec NodeJs,fsmonitor:
Installer
Sudo npm install -g fsmonitor
À partir de la ligne de commande (exemple, journaux de surveillance et "vente au détail" si un fichier journal est modifié)
fsmonitor -s -p '+*.log' sh -c "clear; tail -q *.log"
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 qu'il existe un plugin disponible pour ce que vous essayez de faire en premier lieu.
Sous Linux:
man watch
watch -n 2 your_command_to_run
Lance la commande toutes les 2 secondes.
Si l'exécution de votre commande prend plus de 2 secondes, Watch attend que l'opération soit terminée avant de recommencer.
si vous avez nodemon installé, 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 [email protected]:/var/www" -e ".html"
Watchdogest un projet Python, et peut être tout à fait ce que vous cherchez:
Plateformes supportées
- Linux 2.6 (inotify)
- Mac OS X (FSEvents, kqueue)
- FreeBSD/BSD (kqueue)
- Windows (ReadDirectoryChangesW avec les 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 luiwatchdog_exec
:
Sur l’événement fs impliquant des fichiers et des dossiers du répertoire en cours, exécutez la commande echo $src $dst
, sauf si l’événement fs est modifié, puis exécutez la commande python $src
.
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 une CLI officielle appelée watchmedo
, alors vérifiez-la également.
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).
Amélioré le réponse de Gilles .
Cette version exécute inotifywait
une fois et surveille les événements (par exemple: modify
) par la suite. De telle sorte que inotifywait
not doit ê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
swarminglogic wrote un script appelé watchfile.sh , également disponible en tant que GitHub Gist .
Un peu plus sur 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.
J'aime la simplicité de while inotifywait ...; do ...; done
, mais il a deux problèmes:
do ...;
seront manquantes _J'ai donc créé un script d'assistance qui utilise inotifywait sans ces limitations: inotifyexec
Je vous suggère de mettre ce script dans votre chemin, comme dans ~/bin/
. L'utilisation est décrite en exécutant simplement la commande.
Exemple: inotifyexec "echo test" -r .
Amélioré La solution de Sebastian avec la commande watch
:
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 commande watch
a des bogues connus (voir man): elle réagit aux modifications uniquement dans VISIBLE dans les parties terminales de la sortie -g CMD
.
Pour ceux d'entre vous qui recherchent une solution FreeBSD, voici le port:
/usr/ports/sysutils/wait_on
Vous pouvez essayer réflexe .
Reflex est un petit outil permettant de surveiller un répertoire et de réexécuter une commande lorsque certains fichiers sont modifiés. 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
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 créer, modifier ou supprimer des démons/agents.
( exemple pris à partir 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>
J'ai un Gist pour cela et l'utilisation est assez simple
watchfiles <cmd> <paths...>
https://Gist.github.com/thiagoh/5d8f53bfb64985b94e5bc8b3844dba55
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.
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 sur ~/.kde/share/config/plasma-desktop-appletsrc
; que pour une raison inconnue continue à perdre mon SwitchTabsOnHover=false
Voici une solution qui ne nécessite pas l’installation de logiciels supplémentaires et qui fonctionne tout de suite.
tail -q --follow=name myfile.txt | head -n 0
Cette commande se ferme dans les conditions suivantes:
myfile.txt
après l'exécution de la commande.myfile.txt
est remplacé par un autre après l'exécution de la commandeVous dites que vous utilisez vim, et vim remplacera le fichier lors de l'enregistrement. J'ai testé cela fonctionne avec vim.
Vous pouvez ignorer le résultat de cette commande, il peut mentionner quelque chose comme:
tail: «myfile.txt» a été remplacé; suite à la fin du nouveau fichier
Vous pouvez combiner cela avec timeout
pour renvoyer true ou false. Vous pouvez l'utiliser comme ceci:
délai d'attente 5s bash -c 'queue -q --follow = nom pipe 2>/dev/null | head -n 0 '&& echo a changé || echo timeout
tail
utilise inotify
sous le capot. C'est ainsi que vous obtenez ce comportement asynchrone sophistiqué sans interrogation. Il existe probablement un autre programme unix standard qui utilise inotify
que nous pouvons utiliser plus élégamment.
Parfois, ces commandes se terminent immédiatement, mais si vous les exécutez immédiatement une seconde fois, elles fonctionnent comme indiqué. J'ai commis une erreur quelque part, aidez-moi à corriger ceci.
Sur RHEL, je peux utiliser:
délai d'attente 5s sh -c 'gio monitor pipe | head -n 0 '&& echo a changé || echo timeout
Mais je ne suis pas sûr que ce soit portable.
find
peut faire l'affaire.
while true; do
find /path/to/watched/file -ctime 1s | xargs do-what-you-will
done
find -ctime 1s
imprime le nom du fichier s'il était changé dans le dernier 1s
.
L'outil 'fido' peut être une autre option pour ce besoin. Voir https://www.joedog.org/fido-home/
Pour les personnes qui trouvent ceci en recherchant des modifications dans un fichier particulier, la réponse est beaucoup plus simple (inspiré par la réponse de Gilles ).
Si vous voulez faire quelque chose après 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
Enregistrez ceci sous, par exemple, copy_myfile.sh
et placez le fichier .sh
dans le dossier /etc/init.d/
pour le lancer au démarrage.
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.
Vous pouvez l'installer (si vous avez Python3 et pip) en utilisant:
pip3 install git+https://github.com/vimist/watch-do
Utilisez-le tout de suite en exécutant:
watch-do -w my_file -d 'echo %f changed'
-w '*.py'
ou -w '**/*.py'
)-d
à nouveau)-r
pour l’activer)J'ai écrit un programme Python pour faire exactement cela, appelé rerun
.
UPDATE: Cette réponse est un script Python qui interroge les modifications, ce qui est utile dans certaines circonstances. Pour un script Bash exclusivement Linux qui utilise inotify, consultez mon autre réponse, recherchez "rerun2" dans cette page.
Installez pour Python2 ou Python3 avec:
pip install --user rerun
et l'utilisation est très simple:
rerun "COMMAND"
La commande est attendue sous la forme d'un argument unique et non d'une séquence d'arguments séparés par des espaces. Citez-le comme indiqué, ce qui réduit les fuites supplémentaires que vous auriez à ajouter. Tapez simplement la commande comme vous l’auriez tapée sur la ligne de commande, mais entre guillemets.
Par défaut, il surveille tous les fichiers dans ou sous le répertoire en cours, en ignorant des éléments tels que les répertoires de contrôle de code source connus, .git, .svn, etc.
Les indicateurs facultatifs incluent «-i NAME», qui ignore les modifications apportées aux fichiers ou aux répertoires nommés. Cela peut être donné plusieurs fois.
S'agissant d'un script Python, il doit exécuter la commande en tant que sous-processus. Nous utilisons une nouvelle instance du shell actuel de l'utilisateur pour interpréter 'COMMAND' et décider du processus à exécuter. Cependant, si votre commande contient des alias de shell et similaires définis dans .bashrc, ils ne seront pas chargés par le sous-shell. Pour résoudre ce problème, vous pouvez attribuer à nouveau un indicateur «-I», afin d'utiliser des sous-shell interactifs (ou «login»). C’est plus lent et plus sujet aux erreurs que de démarrer un shell classique, car il doit générer votre fichier .bashrc.
Je l’utilise avec Python 3, mais la dernière fois que j’ai vérifié, j’exécutais toujours avec Python 2.
L'épée à double tranchant est qu'il utilise polling au lieu d'inotify. En revanche, cela signifie que cela fonctionne sur tous les systèmes d'exploitation. De plus, c'est mieux que certaines autres solutions présentées ici en termes d'exécution de la commande donnée une seule fois pour une série de modifications du système de fichiers, et non une fois par fichier modifié, tout en exécutant la commande une seconde fois si des fichiers sont modifiés à nouveau. pendant que la commande est en cours d'exécution.
Par contre, l'interrogation signifie qu'il y a une latence de 0,0 à 1,0 seconde et qu'il est bien sûr lent à surveiller des annuaires extrêmement volumineux. Cela dit, je n’ai jamais rencontré un projet assez important pour que cela soit visible même si vous utilisez '-i' pour ignorer de grandes choses comme virtualenv et node_modules.
Hmmm. rerun
m'est indispensable depuis des années - je l'utilise en fait huit heures par jour pour exécuter des tests, reconstruire des fichiers de points à mesure que je les édite, etc. Mais maintenant, je viens de taper ceci ici, il est clair que je dois passer à un solution qui utilise inotify (je n’utilise plus Windows ni OSX.) et qui est écrite en Bash (elle fonctionne donc avec des alias sans fiddling supplémentaire.)
La description
Ceci surveillera les modifications dans un fichier et exécutera la commande (y compris les arguments supplémentaires) a été donnée en tant que deuxième instruction. Cela effacera également l'écran et imprimera l'heure de la dernière exécution. Remarque: vous pouvez rendre la fonction plus (ou moins) réactive en modifiant le nombre de secondes pendant lesquelles la fonction doit dormir après chaque cycle de boucle while.
Exemple d'utilisation
watch_file my_file.php php my_file.php
Cette ligne va regarder un fichier php my_file.php
et exécuter un interprète php
chaque fois que cela change.
Définition de fonction
function watch_file (){
### Set initial time of file
LTIME=`stat -c %Z $1`
printf "\033c"
echo -e "watching: $1 ---- $(date '+%Y-%m-%d %H:%M:%S')\n-------------------------------------------\n"
${@:2}
while true
do
ATIME=`stat -c %Z $1`
if [[ "$ATIME" != "$LTIME" ]]
then
printf "\033c"
echo -e "watching: $1 ---- $(date '+%Y-%m-%d %H:%M:%S')\n-------------------------------------------\n"
${@:2}
LTIME=$ATIME
fi
sleep 1
done
}
Crédit
Ceci est fondamentalement une version plus générale de la réponse de VDR.