Как выполнить команду при каждом изменении файла?

Мне нужен быстрый и простой способ выполнения команды всякий раз, когда файл изменяется. Я хочу что-то очень простое, что-то я оставлю на терминале и закрываю его, когда я закончу работу с этим файлом.

В настоящее время я использую это:

while read; do ./myfile.py ; done

И тогда мне нужно зайти на этот терминал и нажать Enter , всякий раз, когда я сохраняю этот файл в своем редакторе. Я хочу что-то вроде этого:

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

Или любое другое решение, столь же простое.

BTW: Я использую Vim, и я знаю, что могу добавить автокоманду для запуска чего-то на BufWrite, но это не то решение, которое я хочу сейчас.

Обновление: Я хочу что-то простое, отбрасывание, если это возможно. Более того, я хочу, чтобы что-то запускалось в терминале, потому что я хочу видеть выход программы (я хочу видеть сообщения об ошибках).

Об ответах: Спасибо за все ваши ответы! Все они очень хороши, и каждый из них принимает совсем другой подход от других. Поскольку мне нужно принять только один, я принимаю тот, который я использовал (это было просто, быстро и легко запомнить), хотя я знаю, что это не самый элегантный.

355 голосов | спросил Denilson Sá Maia 28 AM000000120000004031 2010, 00:02:40

30 ответов


340

Простой, используя inotifywait (установите инструменты для индексирования inotify-tools):

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

или

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

Первый фрагмент проще, но он имеет существенный недостаток: он пропустит изменения, выполненные в то время, когда inotifywait не запущен (в частности, пока работает myfile). Второй фрагмент не имеет этого дефекта. Однако будьте осторожны, предполагая, что имя файла не содержит пробелов. Если это проблема, используйте параметр --format, чтобы изменить вывод, чтобы не включать имя файла:

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

В любом случае существует ограничение: если какая-либо программа заменяет myfile.py на другой файл, а не на запись существующего myfile, inotifywait умрет. Многие редакторы работают таким образом.

Чтобы преодолеть это ограничение, используйте inotifywait в каталоге:

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

В качестве альтернативы используйте другой инструмент, который использует те же базовые функции, как incron (позволяет регистрировать события при изменении файла) или fswatch (инструмент который также работает во многих других вариантах Unix, используя аналог каждого варианта Linux inotify).

ответил Gilles 28 AM000000120000005531 2010, 00:54:55
120

entr ( http://entrproject.org/) обеспечивает более дружественный интерфейс для inotify (а также поддерживает * BSD и Mac OS X).

Очень просто указать несколько файлов для просмотра (ограничено только ulimit -n), устраняет проблемы с заменяемыми файлами и требует меньше синтаксиса bash:

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

Я использую его во всем дереве исходных текстов проекта, чтобы запускать модульные тесты для кода, который я сейчас модифицирую, и это уже значительно увеличило мой рабочий процесс.

Флаги, такие как -c (очистить экран между прогонами) и -d (выйти, когда новый файл добавлен в контролируемый каталог), добавляют еще большую гибкость, для Например, вы можете:

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

По состоянию на начало 2018 года он все еще находится в активной разработке, и его можно найти в Debian & Ubuntu (apt install entr); построение из авторского репо было безболезненным в любом случае.

ответил Paul Fenney 25 +04002013-10-25T13:41:16+04:00312013bEurope/MoscowFri, 25 Oct 2013 13:41:16 +0400 2013, 13:41:16
100

Я написал программу Python, чтобы сделать именно это: при изменении .

Использование прост:

when-changed FILE COMMAND...

Или смотреть несколько файлов:

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

FILE может быть каталогом. Смотрите рекурсивно с помощью -r. Используйте %f, чтобы передать имя файла команде.

ответил joh 30 J0000006Europe/Moscow 2011, 17:34:28
45

Как насчет этого скрипта? Он использует команду stat, чтобы получить время доступа к файлу и запускает команду всякий раз, когда происходит изменение времени доступа (при каждом доступе к файлу).

#!/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 20 PM00000090000004731 2013, 21:12:47
28

Решение с использованием Vim:

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

Но я не хочу этого решения, потому что это неприятно печатать, немного сложно запомнить, что вводить, точно, и немного сложно отменить его эффекты (нужно запустить :au! BufWritePost myfile.py). Кроме того, это решение блокирует Vim, пока команда не завершит выполнение.

Я добавил это решение здесь только для полноты, так как это могло бы помочь другим людям.

Чтобы отобразить вывод программы (и полностью нарушить поток редактирования, так как вывод будет записываться через ваш редактор в течение нескольких секунд, пока вы не нажмете Enter), удалите команду :silent.

ответил Denilson Sá Maia 28 AM000000120000002531 2010, 00:12:25
24

Если у вас установлен npm , nodemon , вероятно, самый простой способ начать работу, особенно на OS X, который, по-видимому, не имеет инструментов inotify. Он поддерживает запуск команды при изменении папки.

ответил davidtbernal 10 J0000006Europe/Moscow 2012, 03:51:16
12

Вот простой сценарий оболочки Bourne оболочки, который:

  1. Принимает два аргумента: файл для мониторинга и команду (с аргументами, если необходимо)
  2. Копирует файл, который вы контролируете, в каталог /tmp
  3. Проверяет каждые две секунды, чтобы увидеть, является ли файл, который вы контролируете, новее, чем копия
  4. Если он новее, он перезаписывает копию с новым оригиналом и выполняет команду
  5. Очищает после себя, когда вы нажимаете 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  
    

Это работает на FreeBSD. Единственная проблема с переносимостью, о которой я могу думать, - это если какая-то другая Unix не имеет команды mktemp (1), но в этом случае вы можете просто записать код временного файла.

ответил MikeyMike 28 AM00000010000002931 2010, 01:23:29
12

rerun2 ( на github ) - это 10-строчный скрипт Bash вида:

#!/usr/bin/env bash

function execute() {
    clear
    echo "[email protected]"
    eval "[email protected]"
}

execute "[email protected]"

inotifywait --quiet --recursive --monitor --event modify --format "%w%f" . \
| while read change; do
    execute "[email protected]"
done

Сохраните версию github как «повтор» на вашей PATH и вызовите ее, используя:

rerun COMMAND

Он запускает COMMAND каждый раз, когда в вашем текущем каталоге происходит изменение файла с файловой системой (рекурсивный.)

Вещи, которые могут понравиться:

  • Он использует inotify, поэтому он более отзывчив, чем опрос. Потрясающе для запуска субмиллисекундных модульных тестов или рендеринга графических точечных файлов каждый раз, когда вы нажимаете «save».
  • Поскольку это так быстро, вам не нужно беспокоиться о том, чтобы он игнорировал большие поддиры (например, node_modules) только по соображениям производительности.
  • Это супер супер отзывчиво, потому что он только вызывает inotifywait один раз, при запуске, вместо того, чтобы запускать его, и нести дорогостоящий хит создания часов на каждой итерации.
  • Это всего лишь 12 строк Bash
  • Поскольку это Bash, он интерпретирует команды, которые вы передаете, точно так же, как если бы вы набрали их в приглашении Bash. (Предположительно, это менее круто, если вы используете другую оболочку.)
  • Он не теряет события, которые происходят во время выполнения COMMAND, в отличие от большинства других решений inotify на этой странице.
  • В первом случае он переходит в «мертвый период» на 0,15 секунды, в течение которого другие события игнорируются, прежде чем COMMAND запускается ровно один раз. Это так, что шквал событий, вызванных танцем create-write-move, который Vi или Emacs делает при сохранении буфера, не вызывает много трудоемких исполнений, возможно, медленного набора тестов. Любые события, которые затем происходят во время выполнения COMMAND, не игнорируются - они вызовут второй мертвый период и последующее выполнение.

Вещи, которые могут не понравиться:

  • Он использует inotify, поэтому не будет работать за пределами Linuxland.
  • Поскольку он использует inotify, он будет пытаться смотреть каталоги, содержащие больше файлов, чем максимальное количество пользовательских часов inotify. По умолчанию на разных машинах, которые я использую, по-видимому, установлено около 5000-8000, но их легко увеличить. См. https://unix.stackexchange.com/questions/13751/kernel-inotify-watch -предел-достигла
  • Он не может выполнять команды, содержащие псевдонимы Bash. Я мог бы поклясться, что это сработало. В принципе, поскольку это Bash, не выполняющий COMMAND в подоболочке, я бы ожидал, что это сработает. Я бы хотел услышать, если кто-нибудь знает, почему это не так. Многие другие решения на этой странице также не могут выполнять такие команды.
  • Лично мне жаль, что я не смог нажать ключ в терминале, в котором он работает, чтобы вручную вызвать дополнительное выполнение COMMAND. Могу я добавить это как-то просто? Одновременно выполняется цикл while while -n1, который также вызывает выполнение?
  • Сейчас я закодировал его, чтобы очистить терминал и распечатать выполненную команду на каждой итерации. Некоторым людям может понравиться добавлять флаги командной строки, чтобы отключить такие вещи и т. Д. Но это увеличит размер и сложность многократно.

Это уточнение анкеты @ cychoi.

ответил Jonathan Hartley 10 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowThu, 10 Sep 2015 00:49:14 +0300 2015, 00:49:14
8

Посмотрите incron . Он похож на cron, но использует inotify события вместо времени.

ответил Florian Diesch 28 AM000000120000005931 2010, 00:12:59
8

Для тех, кто не может установить inotify-tools, как я, это должно быть полезно:

watch -d -t -g ls -lR

Эта команда выйдет, когда выход изменится, ls -lR перечислит каждый файл и каталог с его размерами и датами, поэтому, если файл будет изменен, он должен выйти из команды, поскольку человек говорит:

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

Я знаю, что этот ответ не может быть прочитан кем-либо, но я надеюсь, что кто-то достигнет его.

Пример командной строки:

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

Откройте другой терминал:

~ $ echo "testing" > /tmp/test

Теперь первый терминал выведет 1,2,3

Пример простого сценария:

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

watch -d -t -g ls -lR ${DIR_TO_WATCH} && ${COMMAND}
ответил Sebastian 22 MarpmTue, 22 Mar 2016 17:57:03 +03002016-03-22T17:57:03+03:0005 2016, 17:57:03
7

Другое решение с NodeJs, fsmonitor :

  1. Установить

    sudo npm install -g fsmonitor
    
  2. Из командной строки (например, журналы мониторинга и «розничная торговля» при изменении одного файла журнала)

    fsmonitor -s -p '+*.log' sh -c "clear; tail -q *.log"
    
ответил Atika 22 Jpm1000000pmWed, 22 Jan 2014 18:55:46 +040014 2014, 18:55:46
6

В Linux:

man watch

watch -n 2 your_command_to_run

Запустит команду каждые 2 секунды.

Если ваша команда занимает более 2 секунд для запуска, часы будут ждать, пока она не будет выполнена, прежде чем делать это снова.

ответил Eric Leschinski 2 J000000Monday12 2012, 20:36:51
5

Посмотрите на Guard, в частности с этим плагином:

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

Вы можете настроить его для просмотра любого количества шаблонов в каталоге вашего проекта и выполнения команд при внесении изменений. Хороший шанс, даже если есть плагин для этого, что вы пытаетесь сделать в первую очередь.

ответил Wouter Van Vliet 9 J000000Wednesday14 2014, 17:16:10
5

, если у вас установлен nodemon , вы можете сделать это:

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

В моем случае я редактирую html локально и отправляю его на мой удаленный сервер, когда файл изменяется.

nodemon -w <watch directory> -x "scp filename [email protected]:/var/www" -e ".html"
ответил Jay 9 AM00000020000000931 2014, 02:20:09
4

Если ваша программа генерирует какой-то журнал /вывод, вы можете создать Makefile с правилом для этого журнала /вывода, который зависит от вашего скрипта и сделать что-то вроде

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

В качестве альтернативы вы можете создать фальшивую цель и иметь правило, чтобы он вызывал ваш скрипт и касался фальшивой цели (хотя все еще зависит от вашего скрипта).

ответил ctgPi 28 AM000000120000005231 2010, 00:37:52
4
ответил Denilson Sá Maia 16 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowTue, 16 Sep 2014 03:24:58 +0400 2014, 03:24:58
4

Улучшено после ответа Гилла .

Эта версия запускает inotifywait один раз и отслеживает события (.e.g .: modify) после этого. Для того, чтобы inotifywait не выполнялось при каждом встреченном событии.

Это быстро и быстро! (даже при рекурсивном просмотре большой директории)

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
ответил cychoi 17 J0000006Europe/Moscow 2015, 21:03:51
3

Немного больше на стороне программирования, но вы хотите что-то вроде inotify . Существуют версии на многих языках, такие как jnotify и pyinotify .

Эта библиотека позволяет отслеживать отдельные файлы или целые каталоги и возвращает события при обнаружении действия. Возвращенная информация включает в себя имя файла, действие (создание, изменение, переименование, удаление) и путь к файлу, среди прочих полезных сведений.

ответил John T 28 AM000000120000005931 2010, 00:05:59
3

Для тех из вас, кто ищет решение FreeBSD, здесь находится порт:

/usr/ports/sysutils/wait_on
ответил akond 12 12012vEurope/Moscow11bEurope/MoscowMon, 12 Nov 2012 01:48:31 +0400 2012, 01:48:31
3

Мне нравится простота while inotifywait ...; do ...; done, однако у него есть две проблемы:

  • Изменения файла, происходящие во время do ...;, будут пропущены
  • Slow при использовании в рекурсивном режиме

Для этого я создал вспомогательный скрипт, который использует inotifywait без этих ограничений: inotifyexec

Я предлагаю вам поместить этот скрипт в свой путь, например, в ~/bin/. Использование описано только при запуске команды.

Пример: inotifyexec "echo test" -r .

ответил Wernight 29 PMpTue, 29 Apr 2014 17:56:13 +040056Tuesday 2014, 17:56:13
3

Watchdog - это проект Python и может быть именно тем, что вы ищу:

  

Поддерживаемые платформы

     
  • Linux 2.6 (inotify)
  •   
  • Mac OS X (FSEvents, kqueue)
  •   
  • FreeBSD /BSD (kqueue)
  •   
  • Windows (ReadDirectoryChangesW с портами ввода /вывода, рабочие потоки ReadDirectoryChangesW)
  •   
  • Независимо от ОС (опрос диска для моментальных снимков каталога и их периодическое сравнение, медленный и не рекомендуется)
  •   

Просто написал оболочку командной строки для нее watchdog_exec

Пример выполнения

В случае события fs, включающего файлы и папки в текущем каталоге, запустите команду echo $src $dst, если только это не событие fs, а затем выполните команду python $src .

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

Использование коротких аргументов и ограничение на выполнение только в том случае, когда события включают « main .py»:

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

EDIT: только что найденный сторожевой таймер имеет официальный CLI под названием watchmedo, поэтому также проверьте это.

ответил Samuel Marks 28 MonEurope/Moscow2015-12-28T13:44:18+03:00Europe/Moscow12bEurope/MoscowMon, 28 Dec 2015 13:44:18 +0300 2015, 13:44:18
3

Улучшено решение Себастьяна с помощью команды 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

Пример вызова:

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

Это работает, но будьте осторожны: команда watch имеет известные ошибки (см. man): она реагирует на изменения только в VISIBLE в терминальных частях вывода -g CMD.

ответил alex_1948511 12 PMpTue, 12 Apr 2016 17:53:44 +030053Tuesday 2016, 17:53:44
1

Ответ oneliner, который я использую, чтобы отслеживать изменение файла:

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

Вам не нужно инициализировать BF, если вы знаете, что первая дата - это время начала.

Это просто и удобно. Существует другой ответ, основанный на той же стратегии, в которой используется скрипт. Посмотрите также.


Использование: я использую это, чтобы отлаживать и следить за ~/.kde/share/config/plasma-desktop-appletsrc; что по какой-то неизвестной причине продолжает терять мой SwitchTabsOnHover=false

ответил Dr Beco 1 stEurope/Moscowp30Europe/Moscow09bEurope/MoscowTue, 01 Sep 2015 07:53:52 +0300 2015, 07:53:52
1

Я использую этот скрипт для этого. Я использую inotify в режиме мониторинга

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

Сохраните это как runatwrite.sh

Usage: runatwrite.sh myfile.sh

он будет запускать myfile.sh при каждой записи.

ответил Fernando Silva 26 FebruaryEurope/MoscowbFri, 26 Feb 2016 22:10:05 +0300000000pmFri, 26 Feb 2016 22:10:05 +030016 2016, 22:10:05
1

Вы можете попробовать reflex .

Reflex - небольшой инструмент для просмотра каталога и повторной команды при изменении определенных файлов. Это отлично подходит для автоматического запуска задач компиляции /lint /test и для перезагрузки приложения при изменении кода.

# Rerun make whenever a .c file changes
reflex -r '\.c$' make
ответил masterxilo 1 MarpmWed, 01 Mar 2017 23:14:14 +03002017-03-01T23:14:14+03:0011 2017, 23:14:14
0

Для людей, которые находят это в Googling для изменений в конкретном файле, ответ намного проще (вдохновлен

ответил LondonRob 13 Maypm15 2015, 18:21:33
0

Для тех, кто использует OS X, вы можете использовать LaunchAgent для просмотра пути /файла для изменений и делать что-то, когда это произойдет. FYI - LaunchControl - хорошее приложение для легкого создания /изменения /удаления демонов /агентов.

( пример, взятый здесь )

<?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 22 MarpmTue, 22 Mar 2016 18:33:56 +03002016-03-22T18:33:56+03:0006 2016, 18:33:56
0

Инструмент «fido» может быть еще одним вариантом для этой потребности. См. https://www.joedog.org/fido-home/

ответил David Ramirez 20 MarpmMon, 20 Mar 2017 16:52:53 +03002017-03-20T16:52:53+03:0004 2017, 16:52:53
0

У меня есть GIST для этого, и использование довольно просто

watchfiles <cmd> <paths...>

https://gist.github.com/thiagoh/5d8f53bfb64985b94e5bc8b3844dba55

ответил thiagoh 5 AMpWed, 05 Apr 2017 10:38:43 +030038Wednesday 2017, 10:38:43
0

Как и некоторые другие, я также написал легкий инструмент командной строки для этого. Он полностью документирован, протестирован и модулен.

Watch-Do

Установка

Вы можете установить его (если у вас есть Python3 и pip), используя:

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

Использование

Используйте его сразу, выполнив:

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

Обзор функций

  • Поддерживает файловое globbing (используйте -w '*.py' или -w '**/*.py')
  • Запустите несколько команд при изменении файла (просто укажите флаг -d)
  • Динамически поддерживает список файлов для просмотра, если используется глобулизация (-r, чтобы включить это)
  • Несколько способов «смотреть» файл:
    • Время модификации (по умолчанию)
    • Хэш файла
    • Тривиально реализовать свой собственный (это МодификацияTime )
  • Модульная конструкция. Если вы хотите, чтобы команды выполнялись, когда к файлу обращается доступ, тривиально писать собственный наблюдатель (механизм, определяющий, должны ли выполняться исполнители).
ответил vimist 3 J0000006Europe/Moscow 2017, 15:10:57

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132