Come eseguire un comando ogni volta che cambia un file?


434

Voglio un modo rapido e semplice per eseguire un comando ogni volta che un file cambia. Voglio qualcosa di molto semplice, qualcosa che lascerò in esecuzione su un terminale e lo chiuderò ogni volta che avrò finito di lavorare con quel file.

Attualmente sto usando questo:

while read; do ./myfile.py ; done

E poi devo andare su quel terminale e premere Enter, ogni volta che salvo quel file sul mio editor. Quello che voglio è qualcosa del genere:

while sleep_until_file_has_changed myfile.py ; do ./myfile.py ; done

O qualsiasi altra soluzione così semplice.

A proposito: sto usando Vim e so di poter aggiungere un autocomando per eseguire qualcosa su BufWrite, ma questo non è il tipo di soluzione che voglio ora.

Aggiornamento: voglio qualcosa di semplice, scartabile se possibile. Inoltre, voglio che qualcosa venga eseguito in un terminale perché voglio vedere l'output del programma (voglio vedere i messaggi di errore).

Informazioni sulle risposte: grazie per tutte le risposte! Sono tutti molto bravi e ognuno ha un approccio molto diverso dagli altri. Dato che devo accettarne solo uno, sto accettando quello che ho effettivamente usato (era semplice, veloce e facile da ricordare), anche se so che non è il più elegante.


Possibile duplicato su più siti di: stackoverflow.com/questions/2972765/… (anche se qui è sull'argomento =))
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

ho fatto riferimento prima di un duplicato cross-site ed è stato negato: S;)
Francisco Tapia,

4
La soluzione di Jonathan Hartley si basa su altre soluzioni qui e risolve i grandi problemi che hanno le risposte più votate: mancare alcune modifiche ed essere inefficienti. Si prega di cambiare la risposta accettata alla sua, che è anche mantenuta su github su github.com/tartley/rerun2 (o su qualche altra soluzione senza quei difetti)
nealmcb

Risposte:


405

Semplice, usando inotifywait (installa il inotify-toolspacchetto della tua distribuzione ):

while inotifywait -e close_write myfile.py; do ./myfile.py; done

o

inotifywait -q -m -e close_write myfile.py |
while read -r filename event; do
  ./myfile.py         # or "./$filename"
done

Il primo frammento è più semplice, ma ha un aspetto negativo significativo: mancherà le modifiche apportate mentre inotifywaitnon è in esecuzione (in particolare mentre myfileè in esecuzione). Il secondo frammento non presenta questo difetto. Tuttavia, attenzione che presuppone che il nome del file non contenga spazi bianchi. In questo caso, utilizzare l' --formatopzione per modificare l'output in modo da non includere il nome del file:

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

Ad ogni modo, c'è una limitazione: se un programma sostituisce myfile.pyun altro file, anziché scrivere nell'esistente myfile, inotifywaitmorirà. Molti editor funzionano in questo modo.

Per superare questa limitazione, utilizzare inotifywaitnella directory:

inotifywait -e close_write,moved_to,create -m . |
while read -r directory events filename; do
  if [ "$filename" = "myfile.py" ]; then
    ./myfile.py
  fi
done

In alternativa, usa un altro strumento che utilizza la stessa funzionalità sottostante, come incron (ti consente di registrare eventi quando un file viene modificato) o fswatch (uno strumento che funziona anche su molte altre varianti Unix, usando l'analogo di ciascuna variante di inotify di Linux).


46
Ho incapsulato tutto questo (con alcuni trucchi bash) in uno sleep_until_modified.shscript di semplice utilizzo , disponibile su: bitbucket.org/denilsonsa/small_scripts/src
Denilson Sá Maia,

14
while sleep_until_modified.sh derivation.tex ; do latexmk -pdf derivation.tex ; doneè fantastico. Grazie.
Rhys Ulerich,

5
inotifywait -e delete_selfsembra funzionare bene per me.
Kos,

3
È semplice ma presenta due importanti problemi: gli eventi possono essere persi (tutti gli eventi nel ciclo) e l'inizializzazione di inotifywait viene eseguita ogni volta, il che rende questa soluzione più lenta per le cartelle ricorsive di grandi dimensioni.
Wernight,

6
Per qualche motivo while inotifywait -e close_write myfile.py; do ./myfile.py; doneesce sempre senza eseguire il comando (bash e zsh). Perché questo funzioni ho dovuto aggiungere || true, ad esempio: while inotifywait -e close_write myfile.py || true; do ./myfile.py; done
ideasman42

166

entr ( http://entrproject.org/ ) fornisce un'interfaccia più intuitiva per inotificare (e supporta anche * BSD e Mac OS X).

Rende molto semplice specificare più file da guardare (limitato solo da ulimit -n), elimina la seccatura nel gestire i file che vengono sostituiti e richiede meno sintassi bash:

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

Lo sto usando sull'intero albero dei sorgenti del progetto per eseguire i test unitari per il codice che sto modificando ed è stato già un enorme impulso al mio flusso di lavoro.

Flag come -c(svuota lo schermo tra le esecuzioni) e -d(esci quando un nuovo file viene aggiunto a una directory monitorata) aggiungono ancora più flessibilità, ad esempio puoi fare:

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

All'inizio del 2018 è ancora in fase di sviluppo attivo e può essere trovato in Debian e Ubuntu ( apt install entr); la costruzione dal repository dell'autore era comunque indolore.


3
Non gestisce i nuovi file e le loro modifiche.
Wernight,

2
@Wernight - dal 7 maggio 2014 entr ha la nuova -dbandiera; è leggermente più prolisso, ma puoi fare while sleep 1 ; do find . -name '*.py' | entr -d ./myfile.py ; doneper gestire i nuovi file.
Paul Fenney,

1
entr è disponibile anche in repository debian almeno da debian jessie / 8.2 su ...
Peter V. Mørch,

5
il migliore che ho trovato su OS X di sicuro. fswatch prende troppi eventi funky e non voglio passare il tempo a capire perché
dtc

5
Vale la pena notare che entr è disponibile su Homebrew, quindi brew install entrfunzionerà come previsto
jmarceli

108

Ho scritto un programma Python per fare esattamente questo chiamato quando cambiato .

L'uso è semplice:

when-changed FILE COMMAND...

O per guardare più file:

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

FILEpuò essere una directory. Guarda ricorsivamente con -r. Utilizzare %fper passare il nome file al comando.


1
@ysangkok sì, nell'ultima versione del codice :)
joh

4
Ora disponibile da "pip install when-change". Funziona ancora bene. Grazie.
AL Flanagan,

2
Per cancellare prima lo schermo è possibile utilizzare when-changed FILE 'clear; COMMAND'.
Dave James Miller,

1
Questa risposta è molto migliore perché posso farlo anche su Windows. E questo ragazzo in realtà ha scritto un programma per ottenere la risposta.
Wolfpack'08,

4
Buone notizie a tutti! when-changedè ora multipiattaforma! Scopri l'ultima versione 0.3.0 :)
joh

52

Che ne dici di questo script? Utilizza il statcomando per ottenere il tempo di accesso di un file ed esegue un comando ogni volta che si verifica una modifica nel tempo di accesso (ogni volta che si accede al file).

#!/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
Non sarebbe statcaching l'ora di modifica essere una migliore "ogni volta che un file viene modificato" Risposta?
Xen2050,

1
L'esecuzione di stat più volte al secondo causerebbe molte letture sul disco? o la chiamata di sistema fstat renderebbe automaticamente cache queste risposte in qualche modo? Sto cercando di scrivere una sorta di 'orologio grugnito' per compilare il mio codice c ogni volta che
apporto delle

Questo è utile se conosci il nome del file da guardare in anticipo. Meglio sarebbe passare il nome del file allo script. Meglio ancora sarebbe se potessi passare molti nomi di file (ad esempio "mywatch * .py"). Meglio ancora se potesse operare in modo ricorsivo anche sui file nei sottodir, cosa che fanno alcune delle altre soluzioni.
Jonathan Hartley,

5
Nel caso in cui qualcuno si stia chiedendo letture pesanti, ho testato questo script in Ubuntu 17.04 con un sonno di 0,05 secondi e vmstat -dper controllare l'accesso al disco. Sembra che Linux faccia un lavoro fantastico nella memorizzazione nella cache di questo genere di cose: D
Oskenso Kashi

C'è un errore di battitura in "COMANDO", stavo cercando di risolvere, ma SO dice "Modifica non dovrebbe essere inferiore a 6 caratteri"
user337085

30

Soluzione con Vim:

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

Ma non voglio questa soluzione perché è un po 'fastidioso digitare, è un po' difficile ricordare esattamente cosa digitare, ed è un po 'difficile annullare i suoi effetti (è necessario eseguirlo :au! BufWritePost myfile.py). Inoltre, questa soluzione blocca Vim fino al termine dell'esecuzione del comando.

Ho aggiunto questa soluzione qui solo per completezza, in quanto potrebbe aiutare altre persone.

Per visualizzare l'output del programma (e interrompere completamente il flusso di modifica, poiché l'output scriverà sull'editor per alcuni secondi, fino a quando non si preme Invio), rimuovere il :silentcomando.


1
Questo può essere abbastanza buono se combinato con entr(vedi sotto) - fai toccare a vim un file fittizio che entr sta guardando e lascia che entr faccia il resto in background ... o tmux send-keysse ti capita di trovarti in un ambiente simile :)
Paul Fenney,

simpatico! puoi creare una macro per il tuo .vimrcfile
ErichBSchulz

23

Se ti capita di aver npminstallato, nodemonè probabilmente il modo più semplice per iniziare, specialmente su OS X, che apparentemente non ha strumenti di inotify. Supporta l'esecuzione di un comando quando cambia una cartella.


5
Tuttavia, guarda solo i file .js e .coffee.
zelk,

6
La versione attuale sembra supportare qualsiasi comando, ad esempio: nodemon -x "bundle exec rspec" spec/models/model_spec.rb -w app/models -w spec/models
kek

1
Vorrei avere più informazioni, ma osx ha un metodo per tenere traccia delle modifiche, fsevents
ConstantineK

1
Su OS X puoi anche usare Launch Daemons con una WatchPathschiave come mostrato nel mio link.
Adam Johns,

19

Per coloro che non possono installare inotify-toolscome me, questo dovrebbe essere utile:

watch -d -t -g ls -lR

Questo comando uscirà quando l'output cambia, ls -lRelencherà ogni file e directory con le sue dimensioni e date, quindi se un file viene modificato dovrebbe uscire dal comando, come dice man:

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

So che questa risposta potrebbe non essere letta da nessuno, ma spero che qualcuno la raggiunga.

Esempio da riga di comando:

~ $ cd /tmp
~ $ watch -d -t -g ls -lR && echo "1,2,3"

Apri un altro terminale:

~ $ echo "testing" > /tmp/test

Ora verrà emesso il primo terminale 1,2,3

Esempio di script semplice:

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

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

5
Bel trucco. Ho provato e sembra avere un problema quando l'elenco è lungo e il file modificato non rientra nella schermata. Una piccola modifica potrebbe essere qualcosa del genere: watch -d -t -g "ls -lR tmp | sha1sum"
Atle

3
se guardi la tua soluzione ogni secondo, funziona per sempre ed esegui MY_COMMAND solo se alcuni file cambiano: watch -n1 "watch -d -t -g ls -lR && MY_COMMAND"
mnesarco

La mia versione di watch (su Linux, watch from procps-ng 3.3.10) accetta secondi float per il suo intervallo, quindi watch -n0.2 ...eseguirà il polling ogni quinto di secondo. Buono per quei test unitari in meno di millisecondi.
Jonathan Hartley,

15

rerun2( su github ) è uno script Bash a 10 righe del modulo:

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

Salvare la versione di github come 'riesegui' sul PATH e invocarla usando:

rerun COMMAND

Esegue COMANDO ogni volta che c'è un evento di modifica del filesystem all'interno della directory corrente (ricorsivo).

Le cose che potrebbero interessarti:

  • Utilizza inotify, quindi è più reattivo del polling. Favoloso per l'esecuzione di test unitari al di sotto del millisecondo o per il rendering di file dot graphviz, ogni volta che si preme 'salva'.
  • Perché è così veloce, non devi preoccuparti di dirgli di ignorare i sottodirectory di grandi dimensioni (come node_modules) solo per motivi di prestazioni.
  • È super super reattivo, perché chiama inotifywait solo una volta, all'avvio, invece di eseguirlo e incorrere in un costoso colpo di stabilire orologi, ad ogni iterazione.
  • Sono solo 12 linee di Bash
  • Poiché è Bash, interpreta i comandi che gli vengono passati esattamente come se li avessi digitati al prompt di Bash. (Presumibilmente questo è meno bello se usi un'altra shell.)
  • Non perde eventi che si verificano durante l'esecuzione di COMMAND, a differenza della maggior parte delle altre soluzioni di inotify in questa pagina.
  • Al primo evento, inserisce un "periodo morto" per 0,15 secondi, durante il quale vengono ignorati altri eventi, prima che il comando venga eseguito esattamente una volta. Questo è così che la raffica di eventi causati dalla danza create-write-move che Vi o Emacs eseguono durante il salvataggio di un buffer non causano esecuzioni multiple laboriose di una suite di test forse a esecuzione lenta. Tutti gli eventi che si verificano durante l'esecuzione di COMMAND non vengono ignorati, poiché causeranno un secondo periodo morto e la successiva esecuzione.

Cose che potrebbero non piacere a riguardo:

  • Usa inotify, quindi non funzionerà al di fuori di Linuxland.
  • Poiché utilizza inotify, fallirà nel tentativo di guardare directory contenenti più file rispetto al numero massimo di watch inotify dell'utente. Per impostazione predefinita, questo sembra essere impostato da circa 5.000 a 8.000 su diverse macchine che utilizzo, ma è facile da aumentare. Vedi https://unix.stackexchange.com/questions/13751/kernel-inotify-watch-limit-reached
  • Non riesce a eseguire comandi contenenti alias Bash. Potrei giurare che questo funzionava. In linea di principio, poiché si tratta di Bash, che non esegue COMMAND in una subshell, mi aspetto che funzioni. Mi piacerebbe sapere se qualcuno sa perché non lo fa. Molte delle altre soluzioni in questa pagina non possono neanche eseguire tali comandi.
  • Personalmente vorrei poter premere una chiave nel terminale in cui è in esecuzione per causare manualmente un'esecuzione aggiuntiva di COMANDO. Potrei aggiungere questo in qualche modo, semplicemente? Un ciclo 'while read -n1' in esecuzione contemporaneamente che chiama anche esegui?
  • In questo momento l'ho codificato per cancellare il terminale e stampare il comando eseguito su ogni iterazione. Alcune persone potrebbero voler aggiungere flag da riga di comando per disattivare cose come questa, ecc. Ma questo aumenterebbe di dimensioni e complessità molte volte.

Questa è una raffinatezza della risposta di @cychoi.


2
Credo che dovresti usare "$@"invece di $@, al fine di lavorare correttamente con argomenti contenenti spazi. Ma allo stesso tempo si utilizza eval, il che costringe l'utente a ripetere l'esecuzione di una maggiore attenzione durante la quotazione.
Denilson Sá Maia,

Grazie Denilson. Potresti dare un esempio di dove la citazione deve essere fatta con attenzione? L'ho usato nelle ultime 24 ore e non ho visto alcun problema con gli spazi finora, né ho citato con cura nulla - ho appena invocato rerun 'command'. Stai solo dicendo che se ho usato "$ @", quindi l'utente può invocare come rerun commandChe non sembra così utile per me (senza virgolette?): Io in genere non voglio Bash fare qualsiasi elaborazione del comando prima di passarlo per rieseguire. ad es. se il comando contiene "echo $ myvar", allora voglio vedere i nuovi valori di myvar in ogni iterazione.
Jonathan Hartley,

1
Qualcosa del genere rerun foo "Some File"potrebbe rompersi. Ma dal momento che si sta utilizzando eval, può essere riscritto come rerun 'foo "Some File". Si noti che a volte l'espansione del percorso potrebbe introdurre spazi: rerun touch *.fooprobabilmente si interromperà e l'utilizzo rerun 'touch *.foo'avrà una semantica leggermente diversa (l'espansione del percorso avviene una sola volta o più volte).
Denilson Sá Maia,

Grazie per l'aiuto. Sì: si rerun ls "some file"rompe a causa degli spazi. rerun touch *.foo*di solito funziona bene, ma non riesce se i nomi dei file che corrispondono a * .foo contengono spazi. Grazie per avermi aiutato a vedere come rerun 'touch *.foo'ha una semantica diversa, ma sospetto che la versione con virgolette singole sia la semantica che desidero: voglio che ogni iterazione di riesecuzione si comporti come se avessi digitato di nuovo il comando - quindi voglio *.foo essere espanso su ogni iterazione . Proverò i tuoi suggerimenti per esaminare i loro effetti ...
Jonathan Hartley,

Altre discussioni su questo PR ( github.com/tartley/rerun2/pull/1 ) e altri.
Jonathan Hartley,

12

Ecco un semplice script di shell Bourne shell che:

  1. Accetta due argomenti: il file da monitorare e un comando (con argomenti, se necessario)
  2. Copia il file che stai monitorando nella directory / tmp
  3. Controlla ogni due secondi per vedere se il file che stai monitorando è più recente della copia
  4. Se è più recente, sovrascrive la copia con l'originale più recente ed esegue il comando
  5. Si pulisce da solo quando si preme 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  
    

Funziona su FreeBSD. L'unico problema di portabilità che mi viene in mente è se qualche altro Unix non ha il comando mktemp (1), ma in quel caso puoi semplicemente codificare il nome del file temporaneo.


9
Il polling è l'unico modo portatile, ma la maggior parte dei sistemi ha un meccanismo di notifica di modifica dei file (inotify su Linux, kqueue su FreeBSD, ...). Hai un grave problema di quotazione quando lo fai $cmd, ma per fortuna è facilmente risolvibile: elimina la cmdvariabile ed esegui "$@". Lo script non è adatto per il monitoraggio di un file di grandi dimensioni, ma potrebbe essere risolto sostituendolo cpcon touch -r(è necessaria solo la data, non il contenuto). Per quanto riguarda la portabilità, il -nttest richiede bash, ksh o zsh.
Gilles,

8

Dai un'occhiata a incron . È simile a cron, ma utilizza gli eventi inotify invece del tempo.


Questo può essere fatto per funzionare, ma la creazione di una voce incron è un processo piuttosto laborioso rispetto ad altre soluzioni in questa pagina.
Jonathan Hartley,

6

Un'altra soluzione con NodeJs, fsmonitor :

  1. Installare

    sudo npm install -g fsmonitor
    
  2. Dalla riga di comando (esempio, monitorare i registri e "vendita al dettaglio" se un file di registro cambia)

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

Nota a margine: l'esempio potrebbe essere risolto tail -F -q *.log, credo.
Volker Siegel,

Era solo per fare un esempio, tail -fnon è clearil terminale.
Atika,

6

Guarda in Guard, in particolare con questo plugin:

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

Puoi impostarlo per guardare un numero qualsiasi di schemi nella directory del tuo progetto ed eseguire comandi quando si verificano cambiamenti. Buone probabilità anche che ci sia un plugin disponibile per quello che stai cercando di fare in primo luogo.


6

se hai installato nodemon , puoi farlo:

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

Nel mio caso, modifico html localmente e lo spedisco al mio server remoto quando un file cambia.

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

6

Sotto Linux:

man watch

watch -n 2 your_command_to_run

Eseguirà il comando ogni 2 secondi.

Se l'esecuzione del comando richiede più di 2 secondi, l'orologio attenderà fino a quando non viene eseguito prima di ripetere.


È piuttosto semplice, anche se in qualche modo uno spreco, è facile per le attività di sviluppo come apportare modifiche live agli stili.
Xeoncross,

2
Cosa succede quando l'esecuzione del comando richiede più di due secondi?
trentatré

@thirtythreeforty Un rapido esperimento su Ubuntu mostra che watch attenderà i due secondi interi, indipendentemente da quanto tempo impiegherà il comando. FWIW, il periodo di sospensione può essere specificato con '-n', fino a un minimo di 0,1 secondi.
Jonathan Hartley,

5

Watchdog è un progetto Python e potrebbe essere proprio quello che stai cercando:

Piattaforme supportate

  • Linux 2.6 (inotify)
  • Mac OS X (FSEvents, kqueue)
  • FreeBSD / BSD (kqueue)
  • Windows (ReadDirectoryChangesW con porte di completamento I / O; ReadDirectoryChangesW thread di lavoro)
  • Indipendente dal sistema operativo (polling del disco per gli snapshot della directory e confronto periodico; lento e sconsigliato)

Ho appena scritto un wrapper a riga di comando per questo watchdog_exec:

L'esempio funziona

Nell'evento fs che coinvolge file e cartelle nella directory corrente, eseguire il echo $src $dstcomando, a meno che non venga modificato l'evento fs, quindi eseguire il python $srccomando.

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

Utilizzando brevi argomenti e limitando l'esecuzione solo quando gli eventi implicano " main .py":

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

EDIT: Ho appena scoperto che Watchdog ha una CLI ufficiale chiamata watchmedo, quindi controlla anche questo.


4

Se il tuo programma genera una sorta di log / output, puoi creare un Makefile con una regola per quel log / output che dipende dal tuo script e fare qualcosa di simile

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

In alternativa, puoi creare un target fasullo e avere la regola per esso sia chiamare il tuo script che toccare il target fasullo (pur continuando a dipendere dal tuo script).


11
while sleep 1 ; do something ; doneè leggermente meglio di while true ; do something ; sleep 1 ; done. Almeno si ferma facilmente quando si preme Ctrl + C.
Denilson Sá Maia,

La rimozione del sonno causerà un circuito occupato (la CPU genera calore e compromette la durata della batteria su un laptop)?
Steven Lu,

2
@StevenLu: no, il sonno non è un'attesa impegnata. Il problema è che se il sonno è nel corpo, Control-C ucciderà il sonno e il ciclo ricomincerà. L'utilizzo di energia per l'avvio del loop over è insignificante. Provalo tu stesso in un terminale. Devi tenere premuto Control-C affinché funzioni, se hai sonno nel corpo.
Janus Troelsen,

Giusto. Penso di essermi perso e non ho visto che il sonno è ancora presente come condizione del ciclo. Quel piccolo ritocco è davvero fantastico.
Steven Lu,

4

2
Questo è uno script Bash a 200 righe ricco di funzionalità che esegue statil polling sui nomi di file indicati, viene eseguito md5sumsull'output e riesegue il comando specificato se questo valore cambia. Poiché è Bash, sospetto che faccia un buon lavoro nell'eseguire il comando dato esattamente come se fosse stato digitato al prompt di Bash. (Al contrario, la maggior parte delle soluzioni qui scritte in altre lingue non riuscirà a eseguire comandi che, ad esempio, contengono alias di shell come ll)
Jonathan Hartley,

4

Migliorato sulla risposta di Gilles .

Questa versione viene eseguita inotifywaituna volta e monitora gli eventi (.eg:) in modifyseguito. Tale che inotifywait non deve essere rieseguito su ogni evento riscontrato.

È veloce e veloce! (anche durante il monitoraggio ricorsivo di directory di grandi dimensioni)

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

Questa è la migliore risposta sulla pagina per utenti solo Linux. Sostituisci le cose all'interno del ciclo con 'execute $ @' e l'utente potrebbe chiamare questo script passando il proprio comando per eseguirlo. Funziona anche con comandi che contengono alias di shell se lo si genera, usando qualcosa come ". Scriptname COMMAND". Questo troverà comunque lo scriptname sul PERCORSO.
Jonathan Hartley,

Penso che intendi mettere "mentre leggi REPLY"?
Jonathan Hartley,

1
grazie per il chiarimento. Ringraziamenti per il graduale cambiamento! Avrei cancellato quei commenti, ma ovviamente ora non lo farò.
Jonathan Hartley,

3

Un po 'più dal punto di vista della programmazione, ma vuoi qualcosa come inotify . Esistono implementazioni in molte lingue, come jnotify e pyinotify .

Questa libreria consente di monitorare singoli file o intere directory e restituisce eventi quando viene rilevata un'azione. Le informazioni restituite includono il nome del file, l'azione (crea, modifica, rinomina, elimina) e il percorso del file, tra le altre informazioni utili.


3

Per quelli di voi che cercano una soluzione FreeBSD, ecco la porta:

/usr/ports/sysutils/wait_on

3

Mi piace la semplicità, while inotifywait ...; do ...; donetuttavia ha due problemi:

  • Le modifiche ai file che si verificano durante il do ...;saranno perse
  • Lento quando si utilizza in modalità ricorsiva

Pertanto ho creato uno script di supporto che utilizza inotifywait senza tali limitazioni: inotifyexec

Ti suggerisco di mettere questo script nel tuo percorso, come in ~/bin/. L'utilizzo è descritto semplicemente eseguendo il comando.

Esempio: inotifyexec "echo test" -r .


Aggiornato lo script per supportare la corrispondenza del pattern regex.
Wernight,

Entrambi i problemi vengono risolti utilizzando inotifywait in modalità "--monitor". Vedi la risposta di cychoi.
Jonathan Hartley,

3

Migliorata la soluzione di Sebastian con watchcomando:

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

Chiama esempio:

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

Funziona ma fai attenzione: il watchcomando ha dei bug noti (vedi man): reagisce alle modifiche solo in VISIBILE nelle parti terminali -g CMDdell'output.


2

Potresti provare il riflesso .

Reflex è un piccolo strumento per guardare una directory ed eseguire nuovamente un comando quando cambiano determinati file. È ottimo per eseguire automaticamente attività di compilazione / lint / test e per ricaricare l'applicazione quando il codice cambia.

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

Puoi citare / spiegare un po 'lo strumento? Leggi rapidamente come consigliare il software come guida.
bertieb

1

Una risposta oneliner che sto usando per tenere traccia di una modifica del file:

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

Non è necessario inizializzare BF se si sa che la prima data è l'ora di inizio.

Questo è semplice e portatile. C'è un'altra risposta basata sulla stessa strategia usando uno script qui. Dai anche un'occhiata.


Utilizzo: sto usando questo per eseguire il debug e tenere d'occhio ~/.kde/share/config/plasma-desktop-appletsrc; che per qualche ragione sconosciuta continua a perdere il mioSwitchTabsOnHover=false


1

Uso questo script per farlo. Sto usando inotify in modalità monitor

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

Salvalo come runatwrite.sh

Usage: runatwrite.sh myfile.sh

eseguirà myfile.sh ad ogni scrittura.


1

Per coloro che utilizzano OS X, è possibile utilizzare LaunchAgent per guardare un percorso / file per le modifiche e fare qualcosa quando ciò accade. Cordiali saluti - LaunchControl è una buona app per creare / modificare / rimuovere facilmente demoni / agenti.

( esempio preso da qui )

<?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

Per le persone che lo trovano su Google per le modifiche a un determinato file, la risposta è molto più semplice (ispirata alla risposta di Gilles ).

Se vuoi fare qualcosa dopo che un determinato file è stato scritto, ecco come:

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

Salvalo come, ad esempio, copy_myfile.she inserisci il .shfile nella /etc/init.d/cartella per eseguirlo all'avvio.


Condivide il problema con la risposta di Giles che viene eseguito inotifywait su ogni iterazione, il che può non rispondere per la visione ricorsiva di directory molto grandi. Vedi la risposta di cychoi per la correzione di questo.
Jonathan Hartley,


0

Come pochi altri hanno fatto, ho anche scritto uno strumento a riga di comando leggero per farlo. È completamente documentato, testato e modulare.

Watch-Do

Installazione

Puoi installarlo (se hai Python3 e pip) usando:

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

uso

Usalo subito eseguendo:

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

Panoramica delle funzionalità

  • Supporta il globbing dei file (usa -w '*.py'o -w '**/*.py')
  • Esegui più comandi su una modifica di file (basta specificare -dnuovamente il flag)
  • Mantiene dinamicamente l'elenco dei file da guardare se viene utilizzato il globbing ( -rper attivarlo )
  • Diversi modi per "guardare" un file:
    • Tempo di modifica (impostazione predefinita)
    • Hash del file
    • Trivial per implementare il tuo (questo è il watcher ModificationTime )
  • Design modulare. Se si desidera che i comandi vengano eseguiti, quando si accede a un file, è banale scrivere il proprio watcher (meccanismo che determina se i responsabili devono essere eseguiti).
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.