Jak wykonać polecenie przy każdej zmianie pliku?

434

Chcę szybkiego i prostego sposobu wykonania polecenia przy każdej zmianie pliku. Chcę czegoś bardzo prostego, czegoś, co zostawię uruchomionego na terminalu i zamknę go, gdy skończę pracę z tym plikiem.

Obecnie używam tego:

while read; do ./myfile.py ; done

A potem muszę iść do tego terminala i naciskać Enter, ilekroć zapiszę ten plik w moim edytorze. Chcę czegoś takiego:

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

Lub każde inne rozwiązanie tak proste.

BTW: Używam Vima i wiem, że mogę dodać komendę automatyczną, aby uruchomić coś na BufWrite, ale nie jest to rozwiązanie, którego chcę teraz.

Aktualizacja: Chcę coś prostego, jeśli to możliwe, można je odrzucić. Co więcej, chcę, aby coś działało w terminalu, ponieważ chcę zobaczyć wyjście programu (chcę zobaczyć komunikaty o błędach).

O odpowiedziach: Dziękujemy za wszystkie odpowiedzi! Wszystkie są bardzo dobre i każde z nich ma inne podejście niż inne. Ponieważ muszę zaakceptować tylko jeden, akceptuję ten, którego faktycznie użyłem (był prosty, szybki i łatwy do zapamiętania), mimo że wiem, że nie jest on najbardziej elegancki.

Denilson Sá Maia
źródło
Możliwy duplikat witryny: stackoverflow.com/questions/2972765/… (chociaż tutaj jest na temat =))
Ciro Santilli 事件 改造 中心 法轮功 六四 事件
odwoływałem się przed duplikatem witryny krzyżowej i odmówiono jej: S;)
Francisco Tapia
4
Rozwiązanie autorstwa Jonathana Hartleya opiera się na innych rozwiązaniach tutaj i rozwiązuje duże problemy, które mają najczęściej głosowane odpowiedzi: brak niektórych modyfikacji i brak efektywności. Zmień zaakceptowaną odpowiedź na jego, która jest również utrzymywana na github na github.com/tartley/rerun2 (lub na inne rozwiązanie bez tych wad)
nealmcb

Odpowiedzi:

404

Proste, używając inotifywait (zainstaluj inotify-toolspakiet swojej dystrybucji ):

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

lub

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

Pierwszy fragment kodu jest prostszy, ale ma znaczącą wadę: będzie pomijał zmiany dokonane, gdy inotifywaitnie jest uruchomiony (w szczególności podczas myfiledziałania). Drugi fragment nie ma tej wady. Należy jednak pamiętać, że zakłada, że ​​nazwa pliku nie zawiera spacji. Jeśli to jest problem, użyj --formatopcji, aby zmienić wyjście tak, aby nie zawierało nazwy pliku:

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

Tak czy inaczej, istnieje ograniczenie: jeśli jakiś program zastąpi myfile.pyinnym plikiem, zamiast zapisywać w istniejącym myfile, inotifywaitumrze. Wielu redaktorów działa w ten sposób.

Aby obejść to ograniczenie, użyj inotifywaitw katalogu:

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

Alternatywnie, użyj innego narzędzia, które korzysta z tej samej podstawowej funkcji, takiego jak incron (pozwala rejestrować zdarzenia, gdy plik jest modyfikowany) lub fswatch (narzędzie, które działa również na wielu innych wariantach Uniksa, wykorzystując analogię każdego wariantu inotify Linuksa).

Gilles
źródło
46
Podsumowałem to wszystko (z kilkoma sztuczkami bash) w prostym w użyciu sleep_until_modified.shskrypcie, dostępnym na stronie: bitbucket.org/denilsonsa/small_scripts/src
Denilson Sá Maia
14
while sleep_until_modified.sh derivation.tex ; do latexmk -pdf derivation.tex ; donejest fantastyczna. Dziękuję Ci.
Rhys Ulerich,
5
inotifywait -e delete_selfwydaje się działać dobrze dla mnie.
Kos
3
Jest to proste, ale ma dwa ważne problemy: zdarzenia mogą zostać pominięte (wszystkie zdarzenia w pętli), a inicjalizacja inotifywait odbywa się za każdym razem, co spowalnia to rozwiązanie w przypadku dużych folderów rekurencyjnych.
Wernight
6
Z jakiegoś powodu while inotifywait -e close_write myfile.py; do ./myfile.py; donezawsze kończy działanie bez uruchamiania polecenia (bash i zsh). Aby to zadziałało, musiałem dodać || true, np .: while inotifywait -e close_write myfile.py || true; do ./myfile.py; done
ideasman42
166

entr ( http://entrproject.org/ ) zapewnia bardziej przyjazny interfejs do inotify (a także obsługuje * BSD i Mac OS X).

Ułatwia to określenie wielu plików do oglądania (ograniczone tylko przez ulimit -n), eliminuje problemy związane z zastępowaniem plików i wymaga mniejszej składni bash:

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

Używałem go w całym drzewie źródeł projektu, aby przeprowadzać testy jednostkowe kodu, który obecnie modyfikuję, i to już znacznie poprawiło mój przepływ pracy.

Flagi takie jak -c(wyczyść ekran między uruchomieniami) i -d(zakończ, gdy nowy plik zostanie dodany do monitorowanego katalogu) zapewniają jeszcze większą elastyczność, na przykład możesz:

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

Od początku 2018 r. Wciąż jest w fazie rozwoju i można go znaleźć w Debian & Ubuntu ( apt install entr); w każdym razie budowanie z repozytorium autora było bezbolesne.

Paul Fenney
źródło
3
Nie obsługuje nowych plików i ich modyfikacji.
Wernight
2
@Wernight - od 7 maja 2014 r. Entr ma nową -dflagę; jest nieco bardziej skomplikowany, ale możesz while sleep 1 ; do find . -name '*.py' | entr -d ./myfile.py ; doneporadzić sobie z nowymi plikami.
Paul Fenney
1
entr jest również dostępny w repozytoriach debian przynajmniej od debian jessie / 8.2 w dniu ...
Peter V. Mørch
5
na pewno najlepszy na OS X. fswatch chwyta zbyt wiele funky wydarzeń i nie chcę tracić czasu na zastanawianie się dlaczego
dtc
5
Warto zauważyć, że entr jest dostępny na Homebrew, więc brew install entrbędzie działać zgodnie z oczekiwaniami
jmarceli
108

Napisałem program w języku Python, który robi dokładnie to, co nazywa się po zmianie .

Użycie jest proste:

when-changed FILE COMMAND...

Lub obejrzeć wiele plików:

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

FILEmoże być katalogiem. Oglądaj rekurencyjnie za pomocą -r. Służy %fdo przekazania nazwy pliku do polecenia.

joh
źródło
1
@ysangkok tak to robi, w najnowszej wersji kodu :)
joh
4
Teraz dostępny z „instalacji pip po zmianie”. Nadal działa ładnie. Dzięki.
AL Flanagan
2
Aby wyczyścić ekran, możesz najpierw użyć when-changed FILE 'clear; COMMAND'.
Dave James Miller
1
Ta odpowiedź jest o wiele lepsza, ponieważ mogę to zrobić również w systemie Windows. I ten facet napisał program, aby uzyskać odpowiedź.
Wolfpack'08
4
Dobre wieści wszyscy! when-changedjest teraz wieloplatformowy! Sprawdź najnowszą 0.3.0 zwalniającą :)
Jn
52

Co powiesz na ten skrypt? Używa statpolecenia, aby uzyskać czas dostępu do pliku i uruchamia polecenie, gdy nastąpi zmiana czasu dostępu (przy każdym dostępie do pliku).

#!/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
VDR
źródło
2
Czy nie statzmodyfikowanie czasu byłoby lepszą odpowiedzią „za każdym razem, gdy plik się zmienia”?
Xen2050,
1
Czy uruchomienie statystyki wiele razy na sekundę spowoduje wiele odczytów na dysk? a może wywołanie systemowe fstat automatycznie buforuje te odpowiedzi w jakiś sposób? Próbuję napisać coś w rodzaju „chrząknięcia”, aby skompilować mój kod c za każdym razem, gdy wprowadzam zmiany
Oskenso Kashi,
Jest to dobre, jeśli znasz nazwę pliku, który chcesz obejrzeć z góry. Lepiej byłoby przekazać nazwę pliku do skryptu. Jeszcze lepiej byłoby, gdybyś mógł przekazać wiele nazw plików (np. „Mywatch * .py”). Jeszcze lepiej byłoby, gdyby mógł rekurencyjnie działać również na plikach w podkatalogach, co robią niektóre inne rozwiązania.
Jonathan Hartley
5
Na wszelki wypadek, gdy ktoś zastanawia się nad ciężkimi odczytami, przetestowałem ten skrypt w Ubuntu 17.04 ze snem 0,05s i vmstat -dobserwowałem dostęp do dysku. Wygląda na to, że Linux wykonuje fantastyczną robotę w buforowaniu tego rodzaju rzeczy: D
Oskenso Kashi
W „COMMAND” jest literówka, próbowałem to naprawić, ale SO mówi „Edycja nie powinna być mniejsza niż 6 znaków”
user337085
30

Rozwiązanie wykorzystujące Vima:

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

Ale nie chcę tego rozwiązania, ponieważ jest trochę denerwujące w pisaniu, trudno jest dokładnie zapamiętać, co dokładnie wpisać, i trochę trudno jest cofnąć jego efekty (trzeba uruchomić :au! BufWritePost myfile.py). Ponadto to rozwiązanie blokuje Vima, dopóki polecenie nie zakończy wykonywania.

Dodałem tutaj to rozwiązanie tylko dla kompletności, ponieważ może pomóc innym ludziom.

Aby wyświetlić dane wyjściowe programu (i całkowicie zakłócić proces edycji, ponieważ dane wyjściowe będą zapisywane w edytorze przez kilka sekund, dopóki nie naciśniesz Enter), usuń :silentpolecenie.

Denilson Sá Maia
źródło
1
Może to być całkiem miłe w połączeniu z entr(patrz poniżej) - po prostu spraw, aby vim dotknął fikcyjnego pliku, który entr ogląda, i niech entr zrobi resztę w tle ... lub tmux send-keysjeśli zdarzy ci się być w takim środowisku :)
Paul Fenney
miły! możesz utworzyć makro dla swojego .vimrcpliku
ErichBSchulz
23

Jeśli zdarzyło Ci się mieć npmzainstalowany, nodemonjest to prawdopodobnie najłatwiejszy sposób na rozpoczęcie pracy, szczególnie w OS X, który najwyraźniej nie ma narzędzi do inotify. Obsługuje uruchamianie polecenia, gdy zmienia się folder.

davidtbernal
źródło
5
Ogląda jednak tylko pliki .js i .coffee.
zelk
6
Obecna wersja wydaje się obsługiwać dowolne polecenia, na przykład: nodemon -x "bundle exec rspec" spec/models/model_spec.rb -w app/models -w spec/models
kek
1
Chciałbym mieć więcej informacji, ale OSX ma metodę śledzenia zmian, fseventy
ConstantineK
1
W systemie OS X można również używać demonów uruchamiających z WatchPathskluczem, jak pokazano w moim łączu.
Adam Johns
19

Dla tych, którzy nie mogą zainstalować inotify-toolsjak ja, powinno to być przydatne:

watch -d -t -g ls -lR

To polecenie zakończy działanie po zmianie danych wyjściowych, ls -lRwyświetli listę wszystkich plików i katalogów wraz z ich rozmiarem i datami, więc jeśli plik zostanie zmieniony, powinien zakończyć polecenie, jak mówi mężczyzna:

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

Wiem, że nikt nie może przeczytać tej odpowiedzi, ale mam nadzieję, że ktoś do niej sięgnie.

Przykład wiersza poleceń:

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

Otwórz inny terminal:

~ $ echo "testing" > /tmp/test

Teraz pierwszy terminal wyjdzie 1,2,3

Prosty przykład skryptu:

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

watch -d -t -g ls -lR ${DIR_TO_WATCH} && ${COMMAND}
Sebastian
źródło
5
Niezły hack. Testowałem i wydaje się, że ma problem, gdy lista jest długa, a zmieniony plik wychodzi poza ekran. Mała modyfikacja może wyglądać watch -d -t -g "ls -lR tmp | sha1sum"
Atle
3
jeśli patrzysz na swoje rozwiązanie co sekundę, działa ono wiecznie i uruchamia MY_COMMAND tylko po wprowadzeniu pewnych zmian pliku: watch -n1 "watch -d -t -g ls -lR && MY_COMMAND"
mnesarco
Moja wersja watch (w systemie Linux watch from procps-ng 3.3.10) akceptuje liczby zmiennoprzecinkowe dla tego przedziału, dlatego watch -n0.2 ...będzie sondować co piąta sekundę. Dobre dla tych zdrowych testów jednostkowych poniżej milisekundy.
Jonathan Hartley,
15

rerun2( na github ) to 10-liniowy skrypt Bash w formie:

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

Zapisz wersję github jako „uruchom ponownie” na swojej ŚCIEŻCE i wywołaj ją, używając:

rerun COMMAND

Uruchamia POLECENIE za każdym razem, gdy w bieżącym katalogu znajduje się zdarzenie modyfikacji systemu plików (rekurencyjne).

Co może się w tym spodobać:

  • Używa inotify, więc jest bardziej responsywny niż odpytywanie. Doskonale nadaje się do przeprowadzania testów jednostkowych poniżej milisekund lub renderowania plików grafviz za każdym razem, gdy naciskasz „zapisz”.
  • Ponieważ jest tak szybki, nie musisz zawracać sobie głowy nakazaniem mu ignorowania dużych podkatalogów (takich jak moduły_węzłów) tylko ze względu na wydajność.
  • Jest wyjątkowo super responsywny, ponieważ wywołuje inotifywait tylko raz, przy uruchomieniu, zamiast go uruchamiać i ponosząc kosztowny hit tworzenia zegarków, przy każdej iteracji.
  • To tylko 12 linii Bash
  • Ponieważ jest to Bash, interpretuje przekazane polecenia dokładnie tak, jakbyś wpisał je po znaku zachęty Bash. (Prawdopodobnie jest to mniej fajne, jeśli użyjesz innej powłoki).
  • Nie traci zdarzeń, które mają miejsce podczas wykonywania polecenia COMMAND, w przeciwieństwie do większości innych rozwiązań inotify na tej stronie.
  • Przy pierwszym zdarzeniu przechodzi w „martwy okres” na 0,15 sekundy, podczas którego inne zdarzenia są ignorowane, zanim polecenie POLECENIE zostanie uruchomione dokładnie raz. Dzieje się tak dlatego, że gwałtowny rozwój wydarzeń spowodowany tańcem tworzenia-zapisu-ruchu, który Vi lub Emacs robią podczas zapisywania bufora, nie powoduje wielu żmudnych wykonań prawdopodobnie wolno działającego zestawu testów. Wszelkie zdarzenia, które następnie wystąpią podczas wykonywania polecenia COMMAND, nie są ignorowane - spowodują drugi martwy okres i kolejne wykonanie.

Rzeczy, których można by nie lubić:

  • Używa inotify, więc nie będzie działać poza Linuxland.
  • Ponieważ używa on inotify, utrudni to próby oglądania katalogów zawierających więcej plików niż maksymalna liczba zegarków inotify użytkownika. Domyślnie wydaje się być ustawiony na około 5000 do 8000 na różnych komputerach, których używam, ale łatwo go zwiększyć. Zobacz https://unix.stackexchange.com/questions/13751/kernel-inotify-watch-limit-reached
  • Nie wykonuje poleceń zawierających aliasy Bash. Mógłbym przysiąc, że to kiedyś działało. Zasadniczo, ponieważ jest to Bash, a nie wykonywanie POLECENIA w podpowłoce, oczekiwałbym, że to zadziała. Chciałbym usłyszeć, jeśli ktoś wie, dlaczego tak nie jest. Wiele innych rozwiązań na tej stronie również nie może wykonywać takich poleceń.
  • Osobiście chciałbym móc wcisnąć klucz w terminalu, w którym jest uruchomiony, aby ręcznie spowodować dodatkowe wykonanie polecenia COMMAND. Czy mogę to jakoś dodać, po prostu? Równolegle działająca pętla „podczas odczytu -n1”, która również wywołuje wykonywanie?
  • W tej chwili zakodowałem go, aby wyczyścić terminal i drukować wykonane polecenie na każdej iteracji. Niektórzy ludzie mogą chcieć dodać flagi wiersza polecenia, aby wyłączyć takie rzeczy itp. Ale zwiększyłoby to wielokrotnie rozmiar i złożoność.

Jest to udoskonalenie odpowiedzi @'egoioi.

Jonathan Hartley
źródło
2
Uważam, że powinieneś używać "$@"zamiast $@, aby poprawnie pracować z argumentami zawierającymi spacje. Ale jednocześnie używasz eval, co zmusza użytkownika do ponownego uruchomienia, aby zachowywał szczególną ostrożność podczas cytowania.
Denilson Sá Maia,
Dzięki Denilson. Czy możesz podać przykład, gdzie należy ostrożnie cytować? Używałem go przez ostatnie 24 godziny i nie widziałem żadnych problemów ze spacjami dotąd, ani dokładnie cytowany cokolwiek - tylko wywołany jako rerun 'command'. Są po prostu mówiąc, że jeśli kiedyś „$ @”, wówczas użytkownik może powołać jako rerun commandże nie wydaje się przydatne dla mnie (bez cudzysłowów): Ja w ogóle nie chcą Bash zrobić żadnego przetwarzania polecenia przed przekazaniem go uruchomić ponownie. np. jeśli polecenie zawiera „echo $ myvar”, to chcę zobaczyć nowe wartości myvar w każdej iteracji.
Jonathan Hartley,
1
Coś takiego rerun foo "Some File"może się zepsuć. Ale ponieważ używasz eval, możesz go przepisać jako rerun 'foo "Some File". Zauważ, że czasami rozszerzenie ścieżki może wprowadzić spacje: rerun touch *.fooprawdopodobnie się zepsuje, a użycie rerun 'touch *.foo'ma nieco inną semantykę (rozszerzenie ścieżki dzieje się tylko raz lub wiele razy).
Denilson Sá Maia,
Dzięki za pomoc. Tak: rerun ls "some file"psuje się z powodu spacji. rerun touch *.foo*zwykle działa dobrze, ale kończy się niepowodzeniem, jeśli nazwy plików pasujące do * .foo zawierają spacje. Dzięki, że pomogłeś mi zobaczyć, jak rerun 'touch *.foo'różni się semantyka, ale podejrzewam, że wersja z pojedynczymi cudzysłowami jest semantyczna, której chcę: chcę, aby każda iteracja powtórzenia działała tak, jakbym ponownie wpisała polecenie - dlatego chcę *.foo być rozwijana z każdą iteracją . Spróbuję twoich sugestii, aby zbadać ich skutki ...
Jonathan Hartley
Więcej dyskusji na temat tego PR ( github.com/tartley/rerun2/pull/1 ) i innych.
Jonathan Hartley,
12

Oto prosty skrypt powłoki Bourne'a, który:

  1. Pobiera dwa argumenty: monitorowany plik i polecenie (w razie potrzeby z argumentami)
  2. Kopiuje monitorowany plik do katalogu / tmp
  3. Sprawdza co dwie sekundy, czy monitorowany plik jest nowszy niż kopia
  4. Jeśli jest nowszy, nadpisuje kopię nowszym oryginałem i wykonuje polecenie
  5. Czyści po sobie po naciśnięciu 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  
    

Działa to na FreeBSD. Jedynym problemem związanym z przenośnością, jaki mogę wymyślić, jest to, że jakiś inny Uniks nie ma komendy mktemp (1), ale w takim przypadku możesz po prostu na stałe wpisać nazwę pliku tymczasowego.

MikeyMike
źródło
9
Odpytywanie jest jedynym przenośnym sposobem, ale większość systemów ma mechanizm powiadamiania o zmianie plików (inotify w Linux, kqueue w FreeBSD, ...). Masz poważny problem z cytowaniem $cmd, ale na szczęście można to łatwo naprawić: porzuć cmdzmienną i wykonaj "$@". Twój skrypt nie nadaje się do monitorowania dużego pliku, ale można go naprawić, zastępując cpgo touch -r(potrzebujesz tylko daty, a nie zawartości). Jeśli chodzi o przenośność, -nttest wymaga bash, ksh lub zsh.
Gilles
8

Spójrz na incron . Jest podobny do crona, ale używa zdarzeń inotify zamiast czasu.

Florian Diesch
źródło
Można to zrobić, aby zadziałało, ale utworzenie wpisu incron jest dość pracochłonnym procesem w porównaniu do innych rozwiązań na tej stronie.
Jonathan Hartley,
6

Inne rozwiązanie z NodeJs, fsmonitor :

  1. zainstalować

    sudo npm install -g fsmonitor
    
  2. Z wiersza poleceń (na przykład monitoruj dzienniki i „sprzedaż detaliczną”, jeśli zmieni się jeden plik dziennika)

    fsmonitor -s -p '+*.log' sh -c "clear; tail -q *.log"
    
Atika
źródło
Uwaga dodatkowa: tail -F -q *.logmyślę, że przykład można rozwiązać .
Volker Siegel,
To tylko przykład, tail -fnie clearterminal.
Atika
6

Zajrzyj do Guard, w szczególności za pomocą tej wtyczki:

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

Możesz go skonfigurować tak, aby obserwował dowolną liczbę wzorców w katalogu twojego projektu i wykonywał polecenia po wystąpieniu zmian. Jest duża szansa, że ​​dostępna jest wtyczka do tego, co próbujesz zrobić w pierwszej kolejności.

Wouter Van Vliet
źródło
6

jeśli masz zainstalowany nodemon , możesz to zrobić:

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

W moim przypadku edytuję html lokalnie i wysyłam go na mój zdalny serwer, gdy plik się zmienia.

nodemon -w <watch directory> -x "scp filename [email protected]:/var/www" -e ".html"
Sójka
źródło
6

W systemie Linux:

man watch

watch -n 2 your_command_to_run

Uruchomi polecenie co 2 sekundy.

Jeśli wykonanie polecenia trwa dłużej niż 2 sekundy, zegarek poczeka, aż zostanie wykonany, zanim wykona to ponownie.

Eric Leschinski
źródło
To dość proste, choć trochę marnotrawstwo, łatwe do zadań programistycznych, takich jak wprowadzanie zmian stylów na żywo.
Xeoncross,
2
Co się stanie, gdy polecenie uruchomi się dłużej niż dwie sekundy?
trzydzieści trzydzieści
@thirtythreeforty Szybki eksperyment na Ubuntu pokazuje, że zegarek będzie czekać pełne dwie sekundy bez względu na to, jak długo trwa uruchomienie polecenia. FWIW, okres uśpienia można określić za pomocą „-n”, do minimum 0,1 sekundy.
Jonathan Hartley,
5

Watchdog to projekt w języku Python i może być właśnie tym, czego szukasz:

Obsługiwane platformy

  • Linux 2.6 (inotify)
  • Mac OS X (FSEvent, kqueue)
  • FreeBSD / BSD (kqueue)
  • Windows (ReadDirectoryChangesW z portami zakończenia we / wy; ReadDirectoryChangesW wątki robocze)
  • Niezależny od systemu operacyjnego (sondowanie dysku w poszukiwaniu migawek katalogów i okresowe porównywanie; wolne i niezalecane)

Właśnie napisałem dla niego opakowanie wiersza polecenia watchdog_exec:

Przykład działa

W przypadku zdarzenia fs dotyczącego plików i folderów w bieżącym katalogu uruchom echo $src $dstpolecenie, chyba że zdarzenie fs zostanie zmodyfikowane, a następnie uruchom python $srcpolecenie.

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

Używając krótkich argumentów i ograniczając się do wykonywania tylko wtedy, gdy zdarzenia dotyczą „ main .py”:

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

EDYCJA: Właśnie znalazłem, że Watchdog ma oficjalny interfejs CLI watchmedo, więc sprawdź to również.

Samuel Marks
źródło
4

Jeśli twój program generuje jakiś dziennik / dane wyjściowe, możesz utworzyć plik Makefile z regułą dla tego dziennika / danych wyjściowych zależną od skryptu i zrobić coś takiego

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

Alternatywnie możesz utworzyć fałszywy cel i mieć regułę zarówno wywoływania skryptu, jak i dotykania fałszywego celu (wciąż zależnie od skryptu).

ctgPi
źródło
11
while sleep 1 ; do something ; donejest nieco lepszy niż while true ; do something ; sleep 1 ; done. Przynajmniej zatrzymuje się łatwo po naciśnięciu Ctrl + C.
Denilson Sá Maia
Czy usunięcie snu spowoduje zajętość pętli (procesor generujący ciepło i niekorzystnie wpływający na żywotność baterii laptopa)?
Steven Lu
2
@StevenLu: nie, sen nie jest zajęty. Problem polega na tym, że jeśli sen jest w ciele, Control-C zabije sen i pętla zacznie się od nowa. Zużycie energii przy rozpoczynaniu pętli jest nieznaczne. Wypróbuj sam w terminalu. Musisz przytrzymać klawisz Control-C, aby zadziałał, jeśli śpisz w ciele.
Janus Troelsen,
Dobrze. Chyba mi tego brakowało i nie widziałem, że sen jest nadal obecny jako warunek pętli. To małe ulepszenie jest całkiem niesamowite.
Steven Lu,
4

swarminglogic napisał skrypt o nazwie watchfile.sh , dostępny również jako GitHub Gist .

Denilson Sá Maia
źródło
2
Jest to bogaty w funkcje, 200-liniowy skrypt Bash, który odpytuje statpodane nazwy plików, uruchamia md5sumdane wyjściowe i ponownie uruchamia podane polecenie, jeśli ta wartość się zmieni. Ponieważ jest to Bash, podejrzewam, że dobrze wykonuje podane polecenie dokładnie tak, jakbyś wpisał je po znaku zachęty Bash. (Dla kontrastu, większość rozwiązań tutaj napisanych w innych językach nie wykona poleceń, które na przykład zawierają aliasy powłoki, takie jak ll)
Jonathan Hartley
4

Poprawiono odpowiedź Gillesa .

Ta wersja działa inotifywaitraz i monitoruje zdarzenia (.eg:) modifypóźniej. Takie, które inotifywait nie muszą być ponownie wykonywane przy każdym napotkanym zdarzeniu.

Jest szybki i szybki! (nawet podczas rekurencyjnego monitorowania dużego katalogu)

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
kieroi
źródło
Jest to najlepsza odpowiedź na stronie dla użytkowników tylko dla systemu Linux. Zamień elementy wewnątrz pętli na „wykonaj $ @”, a użytkownik może wywołać ten skrypt, przekazując własne polecenie do uruchomienia. Działa nawet z poleceniami, które zawierają aliasy powłoki, jeśli ją źródłeś, używając czegoś takiego jak „. Scriptname COMMAND”. To nadal znajdzie nazwę skryptu na ŚCIEŻCE.
Jonathan Hartley,
Myślę, że masz na myśli „podczas czytania ODPOWIEDŹ”?
Jonathan Hartley,
1
Dziękuję za wyjaśnienie. Dzięki za jego stopniowe wycofanie! Usunąłem te komentarze, ale oczywiście nie zrobię tego.
Jonathan Hartley
3

Trochę więcej po stronie programowania, ale chcesz coś takiego jak inotify . Istnieją implementacje w wielu językach, takie jak jnotify i pyinotify .

Ta biblioteka pozwala monitorować pojedyncze pliki lub całe katalogi i zwraca zdarzenia po wykryciu akcji. Zwrócone informacje obejmują między innymi nazwę pliku, akcję (tworzenie, modyfikację, zmianę nazwy, usuwanie) i ścieżkę do pliku, a także inne przydatne informacje.

John T.
źródło
3

Dla tych z Was, którzy szukają rozwiązania FreeBSD, oto port:

/usr/ports/sysutils/wait_on
akond
źródło
3

Podoba mi się prostota tego, while inotifywait ...; do ...; doneale ma dwa problemy:

  • Zmiany plików zachodzące w trakcie do ...;zostaną pominięte
  • Wolno podczas używania w trybie rekurencyjnym

Dlatego stworzyłem skrypt pomocnika, który używa inotifywait bez tych ograniczeń: inotifyexec

Sugeruję, abyś umieścił ten skrypt na swojej ścieżce, jak w ~/bin/. Użycie opisuje się po prostu uruchamiając polecenie.

Przykład: inotifyexec "echo test" -r .

Wernight
źródło
Zaktualizowano skrypt, aby obsługiwał dopasowanie wzorca wyrażenia regularnego.
Wernight
Oba problemy rozwiązuje się za pomocą inotifywait w trybie „--monitor”. Zobacz odpowiedźoioi.
Jonathan Hartley,
3

Ulepszone rozwiązanie Sebastiana z watchpoleceniem:

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

Przykład połączenia:

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

Działa, ale bądź ostrożny: watchkomenda zna błędy (patrz man): reaguje na zmiany tylko w WIDOCZNYM w końcowych częściach -g CMDwyjścia.

alex_1948511
źródło
2

Możesz spróbować odruchu .

Reflex to małe narzędzie do oglądania katalogu i ponownego uruchamiania polecenia po zmianie niektórych plików. Jest świetny do automatycznego uruchamiania zadań kompilacji / lint / testowania oraz do ponownego ładowania aplikacji, gdy kod się zmienia.

# Rerun make whenever a .c file changes
reflex -r '\.c$' make
masterxilo
źródło
Czy możesz zacytować / wyjaśnić trochę na temat narzędzia? Zapoznaj się szybko z zaleceniami dotyczącymi oprogramowania .
bertieb
1

Odpowiedź oneliner, której używam do śledzenia zmiany pliku:

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

Nie musisz inicjować BF, jeśli wiesz, że pierwsza data to godzina rozpoczęcia.

To jest proste i przenośne. Istnieje inna odpowiedź oparta na tej samej strategii przy użyciu skryptu tutaj. Spójrz także.


Użycie: Używam tego do debugowania i pilnowania ~/.kde/share/config/plasma-desktop-appletsrc; z jakiegoś nieznanego powodu ciągle gubię mojeSwitchTabsOnHover=false

Dr Beco
źródło
1

Używam tego skryptu, aby to zrobić. Używam inotify w trybie monitorowania

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

Zapisz to jako runatwrite.sh

Usage: runatwrite.sh myfile.sh

przy każdym zapisie uruchomi plik myfile.sh.

Fernando Silva
źródło
1

Dla tych, którzy używają OS X, możesz użyć LaunchAgent, aby obserwować ścieżkę / plik pod kątem zmian i zrobić coś, kiedy to nastąpi. FYI - LaunchControl to dobra aplikacja do łatwego tworzenia / modyfikowania / usuwania demonów / agentów.

( przykład wzięty stąd )

<?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>
Hefewe1zen
źródło
0

Dla osób, które Googling znajdują w poszukiwaniu zmian w konkretnym pliku, odpowiedź jest znacznie prostsza (zainspirowana odpowiedzią Gillesa ).

Jeśli chcesz coś zrobić po zapisaniu określonego pliku, oto jak:

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

Zapisz to, na przykład, copy_myfile.shi umieść .shplik w /etc/init.d/folderze, aby uruchomił się przy starcie.

LondonRob
źródło
Dzieli problem z odpowiedzią Gilesa, że ​​działa on inotifywait przy każdej iteracji, co może nie odpowiadać na rekurencyjne oglądanie bardzo dużych katalogów. Zobacz odpowiedź naoioi na naprawę tego.
Jonathan Hartley,
0

Narzędzie „fido” może być kolejną opcją dla tej potrzeby. Zobacz https://www.joedog.org/fido-home/

David Ramirez
źródło
Przeczytaj Jak polecić oprogramowanie, aby uzyskać porady dotyczące tego, jak powinieneś polecać oprogramowanie. Powinieneś podać przynajmniej link, dodatkowe informacje o samym oprogramowaniu oraz o tym, jak można go użyć do rozwiązania problemu w pytaniu.
DavidPostill
0

Jak zrobiło to kilka innych, napisałem również lekkie narzędzie wiersza poleceń, aby to zrobić. Jest w pełni udokumentowany, przetestowany i modułowy.

Watch-Do

Instalacja

Możesz go zainstalować (jeśli masz Python3 i pip), używając:

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

Stosowanie

Użyj go od razu, uruchamiając:

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

Przegląd funkcji

  • Obsługuje globowanie plików (użyj -w '*.py'lub -w '**/*.py')
  • Uruchom wiele poleceń przy zmianie pliku ( -dponownie określ flagę)
  • Dynamicznie utrzymuje listę plików do obejrzenia, jeśli używane jest globowanie ( -raby to włączyć)
  • Wiele sposobów „oglądania” pliku:
    • Czas modyfikacji (domyślnie)
    • Skrót pliku
    • Trywialne wdrożenie własnego (jest to obserwator ModificationTime )
  • Modułowa konstrukcja. Jeśli chcesz, aby komendy były uruchamiane, podczas uzyskiwania dostępu do pliku, trywialne jest napisanie własnego obserwatora (mechanizm, który określa, czy należy wykonać).
vimist
źródło