Frage Wie führe ich einen Befehl aus, wenn sich eine Datei ändert?


Ich möchte eine schnelle und einfache Möglichkeit, einen Befehl auszuführen, wenn sich eine Datei ändert. Ich möchte etwas sehr einfaches, etwas, das ich auf einem Terminal laufen lassen werde, und es schließen, wenn ich mit der Datei fertig bin.

Derzeit verwende ich das:

while read; do ./myfile.py ; done

Und dann muss ich zu diesem Terminal gehen und drücken Eingeben, wenn ich diese Datei auf meinem Editor speichere. Was ich will, ist etwa so:

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

Oder irgendeine andere Lösung so einfach.

BTW: Ich benutze Vim, und ich weiß, dass ich einen automatischen Befehl hinzufügen kann, um etwas auf BufWrite auszuführen, aber das ist nicht die Art von Lösung, die ich jetzt will.

Aktualisieren: Ich möchte etwas Einfaches, wenn möglich verwerfbar. Außerdem möchte ich, dass etwas in einem Terminal ausgeführt wird, weil ich die Programmausgabe sehen möchte (ich möchte Fehlermeldungen sehen).

Über die Antworten: Danke für all deine Antworten! Alle von ihnen sind sehr gut, und jeder nimmt einen ganz anderen Ansatz als die anderen. Da ich nur eins akzeptieren muss, akzeptiere ich das, das ich tatsächlich benutzt habe (es war einfach, schnell und einfach zu erinnern), obwohl ich weiß, dass es nicht das eleganteste ist.


357
2017-08-27 20:02


Ursprung


Mögliches Cross-Site-Duplikat von: stackoverflow.com/questions/2972765/... (obwohl hier ist es am Thema =)) - Ciro Santilli 新疆改造中心 六四事件 法轮功
Ich habe vor einem Cross Site Duplikat referenziert und es wurde abgelehnt: S;) - Francisco Tapia
Die Lösung von Jonathan Hartley baut hier auf anderen Lösungen auf und behebt große Probleme, die die am besten bewerteten Antworten haben: einige Modifikationen fehlen und ineffizient sein. Bitte ändere die angenommene Antwort auf seine, die auch bei github at gepflegt wird github.com/tartley/rerun2 (oder zu einer anderen Lösung ohne diese Fehler) - nealmcb


Antworten:


Einfach, mit inotifywait (Installieren Sie Ihre Distribution inotify-tools Paket):

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

oder

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

Das erste Snippet ist einfacher, aber es hat einen erheblichen Nachteil: es wird Änderungen verpassen, die während durchgeführt wurden inotifywait läuft nicht (insbesondere während myfile läuft). Das zweite Snippet hat diesen Fehler nicht. Beachten Sie jedoch, dass davon ausgegangen wird, dass der Dateiname kein Leerzeichen enthält. Wenn das ein Problem ist, benutze das --format Option, um die Ausgabe so zu ändern, dass der Dateiname nicht enthalten ist:

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

In jedem Fall gibt es eine Einschränkung: wenn ein Programm ersetzt wird myfile.py mit einer anderen Datei, anstatt in das vorhandene zu schreiben myfile, inotifywait wird sterben. Viele Redakteure arbeiten so.

Um diese Einschränkung zu überwinden, verwenden Sie inotifywait auf dem Verzeichnis:

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

Verwenden Sie alternativ ein anderes Tool, das dieselbe zugrunde liegende Funktionalität verwendet, z Inkron (Ermöglicht das Registrieren von Ereignissen, wenn eine Datei geändert wird) oder fswatch (ein Tool, das auch auf vielen anderen Unix-Varianten funktioniert, wobei das Analogon von jeder Variante von Linux inotify verwendet wird).


341
2017-08-27 20:54



Ich habe all dies (mit ein paar Bash-Tricks) in einem einfach zu bedienenden zusammengefasst sleep_until_modified.sh Skript, verfügbar unter: bitbucket.org/denilsonsa/small_scripts/src - Denilson Sá Maia
while sleep_until_modified.sh derivation.tex ; do latexmk -pdf derivation.tex ; done ist fantastisch. Vielen Dank. - Rhys Ulerich
inotifywait -e delete_self scheint gut für mich zu funktionieren. - Kos
Es ist einfach, hat aber zwei wichtige Probleme: Ereignisse können verpasst werden (alle Ereignisse in der Schleife) und die Initialisierung von inotifywait erfolgt jedes Mal, wodurch diese Lösung für große rekursive Ordner langsamer wird. - Wernight
Aus irgendeinem Grund while inotifywait -e close_write myfile.py; do ./myfile.py; done immer beendet, ohne den Befehl auszuführen (bash und zsh). Damit dies funktioniert, musste ich hinzufügen || true, z.B: while inotifywait -e close_write myfile.py || true; do ./myfile.py; done - ideasman42


entr (http://entroproject.org/) bietet eine freundlichere Schnittstelle zu inotify (und unterstützt auch * BSD & Mac OS X).

Es macht es sehr einfach, mehrere zu betrachtende Dateien anzugeben (nur begrenzt durch ulimit -n), erspart den Umgang mit zu ersetzenden Dateien und benötigt weniger Bash Syntax:

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

Ich habe es in meinem gesamten Projektquellbaum verwendet, um die Komponententests für den Code auszuführen, den ich gerade modifiziere, und es hat meinen Workflow bereits enorm verbessert.

Flaggen mögen -c (löschen Sie den Bildschirm zwischen den Läufen) und -d (Beenden, wenn eine neue Datei zu einem überwachten Verzeichnis hinzugefügt wird) Fügen Sie noch mehr Flexibilität hinzu, zum Beispiel:

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

Ab Anfang 2018 ist es noch in aktiver Entwicklung und es kann in Debian & Ubuntu gefunden werden (apt install entr); das Bauen aus dem Repo des Autors war in jedem Fall schmerzfrei.


120
2017-10-25 09:41



Behandelt keine neuen Dateien und deren Änderungen. - Wernight
@Wernight - seit dem 7. Mai 2014 hat entr das neue -d Flagge; es ist etwas langatmiger, aber Sie können es tun while sleep 1 ; do find . -name '*.py' | entr -d ./myfile.py ; done mit neuen Dateien umgehen. - Paul Fenney
verfügbar in aur aur.archlinux.org/packages/tr - Victor Häggqvist
Das beste, das ich auf dem OS X sicher gefunden habe. fswatch ergreift zu viele funky Ereignisse und ich möchte nicht die Zeit verbringen, um herauszufinden, warum - dtc
Es ist erwähnenswert, dass entr ist auf Homebrew, also verfügbar brew install entr wird funktionieren wie erwartet - jmarceli


Ich habe ein Python-Programm geschrieben, um genau das zu tun, genannt wenn geändert.

Die Verwendung ist einfach:

when-changed FILE COMMAND...

Oder um mehrere Dateien zu sehen:

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

FILE kann ein Verzeichnis sein. Beobachten Sie rekursiv mit -r. Benutzen %f um den Dateinamen an den Befehl zu übergeben.


100
2018-06-30 13:34



@ysangkok ja es tut, in der neuesten Version des Codes :) - joh
Jetzt verfügbar von "pip install when-changed". Funktioniert immer noch gut. Vielen Dank. - A. L. Flanagan
Um den Bildschirm zuerst zu löschen, können Sie verwenden when-changed FILE 'clear; COMMAND'. - Dave James Miller
Diese Antwort ist so viel besser, weil ich es auch unter Windows machen kann. Und dieser Typ hat tatsächlich ein Programm geschrieben, um die Antwort zu bekommen. - Wolfpack'08
Gute Neuigkeiten alle zusammen! when-changed ist jetzt plattformübergreifend! Schau dir das Neueste an 0.3.0 Veröffentlichung :) - joh


Wie wäre es mit diesem Skript? Es nutzt die stat Befehl, um die Zugriffszeit einer Datei abzurufen und einen Befehl auszuführen, wenn sich die Zugriffszeit ändert (immer wenn auf die Datei zugegriffen wird).

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



Würde nicht stat-die modifizierte Zeit ein besser sein "wann immer eine Datei ändert" antworten? - Xen2050
Würde die Ausführung von Stat viele Male pro Sekunde viele Lesevorgänge auf der Festplatte verursachen? Oder würde der fstat-Systemaufruf diese Antworten automatisch zwischenspeichern? Ich versuche, eine Art "grunt watch" zu schreiben, um meinen c-Code zu kompilieren, wenn ich Änderungen mache - Oskenso Kashi
Das ist gut, wenn Sie den Dateinamen kennen, der im Voraus angesehen werden soll. Besser wäre es, den Dateinamen an das Skript zu übergeben. Besser wäre es noch, wenn Sie viele Dateinamen übergeben könnten (zB "mywatch * .py"). Besser wäre es noch, wenn es rekursiv auch auf Dateien in Unterverzeichnissen operieren könnte, was einige der anderen Lösungen tun. - Jonathan Hartley
Für den Fall, dass sich jemand über schwere Lesevorgänge wundern sollte, habe ich dieses Skript in Ubuntu 17.04 mit einem Schlaf von 0.05s und getestet vmstat -d auf den Festplattenzugriff achten. Es scheint, dass Linux eine fantastische Arbeit leistet, wenn es um solche Dinge geht: D - Oskenso Kashi
Es gibt einen Tippfehler in "COMMAND", den ich beheben wollte, aber S.O. sagt "Edit sollte nicht weniger als 6 Zeichen sein" - user337085


Lösung mit Vim:

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

Aber ich möchte diese Lösung nicht, weil es ein bisschen nervig ist zu tippen, es ist ein bisschen schwer sich daran zu erinnern, was genau zu tippen ist, und es ist ein bisschen schwierig, seine Effekte rückgängig zu machen (muss laufen) :au! BufWritePost myfile.py). Darüber hinaus blockiert diese Lösung Vim, bis der Befehl ausgeführt wurde.

Ich habe diese Lösung hier nur der Vollständigkeit halber hinzugefügt, da sie anderen Menschen helfen könnte.

Um die Programmausgabe anzuzeigen (und den Bearbeitungsfluss vollständig zu unterbrechen, da die Ausgabe für einige Sekunden über den Editor geschrieben wird, bis Sie die Eingabetaste drücken), entfernen Sie die Option :silent Befehl.


28
2017-08-27 20:12



Dies kann sehr gut sein, wenn es mit kombiniert wird entr (siehe unten) - einfach vim eine Dummy-Datei anfassen, die entr beobachtet, und den Rest im Hintergrund tun lassen ... oder tmux send-keys wenn du in einer solchen Umgebung bist :) - Paul Fenney
nett! Sie können ein Makro für Ihr erstellen .vimrc Datei - ErichBSchulz


Wenn du zufällig hast npm Eingerichtet, nodemon ist wahrscheinlich der einfachste Weg, um loszulegen, besonders unter OS X, das anscheinend keine Inotify-Tools hat. Es unterstützt die Ausführung eines Befehls, wenn sich ein Ordner ändert.


24
2018-06-09 23:51



Es werden jedoch nur .js- und .coffee-Dateien angezeigt. - zelk
Die aktuelle Version scheint jeden Befehl zu unterstützen, zum Beispiel: nodemon -x "bundle exec rspec" spec/models/model_spec.rb -w app/models -w spec/models - kek
Ich wünschte, ich hätte mehr Informationen, aber osx hat eine Methode, Änderungen zu verfolgen, fsevents - ConstantineK
Unter OS X können Sie auch verwenden Starten Sie Dämonen mit einem WatchPaths Schlüssel wie in meinem Link gezeigt. - Adam Johns


Hier ist ein einfaches Shell-Bourne-Shell-Skript, das:

  1. Nimmt zwei Argumente an: die zu überwachende Datei und einen Befehl (ggf. mit Argumenten)
  2. Kopiert die überwachte Datei in das Verzeichnis / tmp
  3. Überprüft alle zwei Sekunden, ob die überwachte Datei neuer ist als die Kopie
  4. Wenn es neuer ist, überschreibt es die Kopie mit dem neueren Original und führt den Befehl aus
  5. Reinigt nach sich selbst, wenn Sie Ctr-C drücken

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

Dies funktioniert unter FreeBSD. Das einzige Portabilitätsproblem, das ich mir vorstellen kann, ist, wenn ein anderes Unix nicht den mktemp (1) -Befehl hat, aber in diesem Fall können Sie nur den temporären Dateinamen fest codieren.


12
2017-08-27 21:23



Polling ist der einzige portable Weg, aber die meisten Systeme haben einen Benachrichtigungsmechanismus für Dateiänderungen (inotify unter Linux, kqueue unter FreeBSD, ...). Sie haben ein schwerwiegendes Angebotsproblem, wenn Sie dies tun $cmd, aber zum Glück ist das leicht zu beheben: Graben Sie die cmd variabel und ausführen "$@". Ihr Skript eignet sich nicht zum Überwachen einer großen Datei, aber das könnte durch Ersetzen behoben werden cp durch touch -r (Sie brauchen nur das Datum, nicht den Inhalt). Tragbarkeit, die -nt Test erfordert bash, ksh oder zsh. - Gilles


rerun2 (auf GitHub) ist ein 10-zeiliges Bash-Skript des Formulars:

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

Speichern Sie die Github-Version als 'rerun' auf Ihrem PATH, und rufen Sie sie auf:

rerun COMMAND

Es führt COMMAND jedes Mal aus, wenn ein Dateisystemänderungsereignis in Ihrem aktuellen Verzeichnis vorhanden ist (rekursiv).

Dinge, die man daran mögen könnte:

  • Es verwendet inotify, reagiert also besser als Polling. Fabelhaft für das Ausführen von Sub-Millisekunden-Unit-Tests oder das Rendern von Graphviz-Dot-Dateien bei jedem Drücken von "Speichern".
  • Weil es so schnell ist, müssen Sie es sich nicht leisten, großen Unterverzeichnissen (wie node_modules) nur aus Leistungsgründen zu ignorieren.
  • Es ist besonders reaktionsschnell, da es beim Starten nur einmal inotifywait aufruft, anstatt es zu starten, und bei jeder Iteration den teuren Hit des Einrichtens von Uhren eingeht.
  • Es sind nur 12 Zeilen Bash
  • Da es Bash ist, interpretiert es Befehle, die Sie übergeben, genau so, als ob Sie sie an einer Bash-Eingabeaufforderung eingegeben hätten. (Vermutlich ist das weniger cool, wenn Sie eine andere Shell verwenden.)
  • Es verliert keine Ereignisse, die während der Ausführung von COMMAND auftreten, im Gegensatz zu den meisten anderen Inotify-Lösungen auf dieser Seite.
  • Beim ersten Ereignis wird für 0,15 Sekunden eine "tote Periode" eingegeben, während der andere Ereignisse ignoriert werden, bevor COMMAND genau einmal ausgeführt wird. Dies ist so, dass das Aufwirbeln von Ereignissen, die durch den Erschaffen-Schreiben-Bewegen-Tanz verursacht werden, was Vi oder Emacs beim Speichern eines Puffers tun, nicht mehrere mühsame Ausführungen einer möglicherweise langsam laufenden Testsuite verursachen. Alle Ereignisse, die während der Ausführung von COMMAND auftreten, werden nicht ignoriert - sie verursachen eine zweite Dead-Periode und nachfolgende Ausführung.

Dinge, die man daran nicht mögen mag:

  • Es nutzt inotify, wird also außerhalb von Linuxland nicht funktionieren.
  • Da es inotify verwendet, wird es nicht versuchen, Verzeichnisse zu beobachten, die mehr Dateien enthalten als die maximale Anzahl von Benutzern, die Uhren inotifizieren. Standardmäßig scheint dies auf verschiedenen Computern, die ich verwende, auf 5000 bis 8000 eingestellt zu sein, ist aber leicht zu erhöhen. Sehen https://unix.stackexchange.com/questions/13751/kernel-otify-watch-limit-reached
  • Es führt keine Befehle aus, die Bash-Aliase enthalten. Ich könnte schwören, dass das früher funktioniert hat. Da dies Bash ist und COMMAND in einer Subshell nicht ausgeführt wird, würde das im Prinzip funktionieren. Ich würde gerne hören, wenn jemand weiß, warum das nicht so ist. Viele der anderen Lösungen auf dieser Seite können solche Befehle auch nicht ausführen.
  • Persönlich wünschte ich, ich könnte einen Schlüssel in dem Terminal drücken, in dem es läuft, um eine zusätzliche Ausführung von COMMAND manuell zu verursachen. Kann ich das irgendwie einfach hinzufügen? Eine gleichzeitig laufende 'while read -n1' Schleife, die auch execute aufruft?
  • Im Moment habe ich es codiert, um das Terminal zu löschen und das ausgeführte COMMAND bei jeder Iteration zu drucken. Einige Leute möchten vielleicht Befehlszeilen-Flags hinzufügen, um solche Dinge auszuschalten usw. Aber das würde die Größe und Komplexität um ein Vielfaches erhöhen.

Dies ist eine Verfeinerung von @ Cychois Antwort.


12
2017-09-09 21:49



Ich glaube, du solltest es benutzen "$@" Anstatt von $@, um mit Argumenten zu arbeiten, die Leerzeichen enthalten. Aber zur gleichen Zeit benutzt du eval, die den Benutzer des Wiederholungslaufs zwingt, besonders vorsichtig zu sein, wenn er zitiert wird. - Denilson Sá Maia
Danke Denilson. Könnten Sie ein Beispiel dafür geben, wo genau zitiert werden muss? Ich benutze es die letzten 24 Stunden und habe bis jetzt noch keine Probleme mit Räumen gesehen vorsichtig zitierte alles - nur aufgerufen als rerun 'command'. Sagst du nur, wenn ich "$ @" verwende, dann könnte der Benutzer als aufrufen rerun command (ohne Anführungsstriche?) Das scheint mir nicht so nützlich zu sein: Ich will generell nicht, dass Bash es tut irgendein Verarbeitung des Befehls vor dem Weiterleiten an die erneute Ausführung. z.B. Wenn der Befehl "echo $ myvar" enthält, möchte ich die neuen Werte von myvar in jeder Iteration sehen. - Jonathan Hartley
Etwas wie rerun foo "Some File" könnte brechen. Aber seit du benutzt evalEs kann wie neu geschrieben werden rerun 'foo "Some File". Beachten Sie, dass die Pfaderweiterung manchmal Leerzeichen einfügen kann: rerun touch *.foo wird wahrscheinlich brechen und verwenden rerun 'touch *.foo' hat etwas andere Semantik (Pfaderweiterung passiert nur einmal oder mehrmals). - Denilson Sá Maia
Danke für die Hilfe. Ja: rerun ls "some file" bricht wegen der Räume. rerun touch *.foo* funktioniert in der Regel gut, schlägt aber fehl, wenn die Dateinamen, die * .foo entsprechen, Leerzeichen enthalten. Danke, dass du mir geholfen hast zu sehen, wie rerun 'touch *.foo' hat andere Semantik, aber ich vermute, dass die Version mit einfachen Anführungszeichen die Semantik ist, die ich möchte: Ich möchte, dass jede Iteration von Wiederholungen so wirkt, als würde ich den Befehl erneut eingeben - daher ich wollen  *.foo bei jeder Iteration erweitert werden. Ich werde Ihre Vorschläge versuchen, um ihre Auswirkungen zu untersuchen ... - Jonathan Hartley
Weitere Diskussionen zu dieser PR (github.com/tartley/rerun2/pull/1) und andere. - Jonathan Hartley


Schau es dir an Inkron. Es ist ähnlich wie cron, verwendet aber Ereignisse anstelle von Zeit.


8
2017-08-27 20:12



Dies kann gemacht werden, um zu arbeiten, aber ein Inkron-Eintrag zu erstellen ist ein sehr arbeitsintensiver Prozess im Vergleich zu anderen Lösungen auf dieser Seite. - Jonathan Hartley


Für diejenigen, die nicht installieren können inotify-tools wie ich, sollte das nützlich sein:

watch -d -t -g ls -lR

Dieser Befehl wird beendet, wenn sich der Ausgang ändert. ls -lR listet jede Datei und jedes Verzeichnis mit ihrer Größe und ihren Daten auf. Wenn eine Datei geändert wird, sollte sie den Befehl beenden, wie ein Mann sagt:

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

Ich weiß, dass diese Antwort von niemandem gelesen werden kann, aber ich hoffe, dass jemand darauf zugreift.

Befehlszeilenbeispiel:

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

Öffne ein anderes Terminal:

~ $ echo "testing" > /tmp/test

Jetzt wird das erste Terminal ausgegeben 1,2,3

Einfaches Skriptbeispiel:

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

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

8
2018-03-22 14:57



Netter Hack. Ich habe getestet und es scheint ein Problem zu haben, wenn die Auflistung lang ist und die geänderte Datei außerhalb des Bildschirms liegt. Eine kleine Änderung könnte in etwa so aussehen: watch -d -t -g "ls -lR tmp | sha1sum" - Atle
Wenn du deine Lösung jede Sekunde ansiehst, funktioniert sie für immer und MY_COMMAND wird nur ausgeführt, wenn sich eine Datei ändert: watch -n1 "watch -d -t -g ls -lR && MY_COMMAND" - mnesarco
Meine Version der Uhr (Unter Linux, watch from procps-ng 3.3.10) akzeptiert Float Sekunden für sein Intervall watch -n0.2 ... wird jede fünfte Sekunde abfragen. Gut für diese gesunden Teil-Millisekunden-Unit-Tests. - Jonathan Hartley


Eine andere Lösung mit NodeJs, fsmonitor :

  1. Installieren 

    sudo npm install -g fsmonitor
    
  2. Über die Befehlszeile (Beispiel: Überwachung von Protokollen und "Verkauf", wenn sich eine Protokolldatei ändert)

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

7
2018-01-22 14:55



Randnotiz: Das Beispiel könnte gelöst werden durch tail -F -q *.log, Ich denke. - Volker Siegel