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

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

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

while read; do ./myfile.py ; done 

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

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

Или любое другое решение так просто.

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

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

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

  • Как позволить утилите `man` использовать` less` для отображения руководства, а не `more`?
  • Основной вопрос * nix (Mac OS X): Где мой путь установлен?
  • Ls -la symbolics ... что означает этот последний символ?
  • Можно ли перенести процесс переднего плана на задний план без приостановки (control + z)?
  • Что такое файл .bashrc?
  • Я не совсем понимаю разрешения файлов CHMOD, может ли кто-нибудь лучше объяснить это?
  • Постоянная повторная попытка возобновить загрузку с помощью завитка
  • Команды Unix для получения последней измененной даты и размера файла / папки (NOT LS)
  • 26 Solutions collect form web for “Как выполнить команду при каждом изменении файла?”

    Простой, используя 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, используя аналог каждого варианта inotify Linux).

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

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

     when-changed FILE COMMAND... 

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

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

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

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

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

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

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

    По состоянию на май 2014 года он все еще находится в активной разработке, с новыми флагами, такими как -c (очистка экрана между прогонами) и -d (выход, когда новый файл добавляется в контролируемый каталог).

    entr теперь также можно найти в Debian Jessie / Sid, хотя он еще не имеет флага -d . (Так или иначе, создание авторского репо было безболезненным).

    Как насчет этого скрипта? Он использует команду 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 COMMNAD" LTIME=$ATIME fi sleep 5 done 

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

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

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

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

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

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

    Вот простой сценарий оболочки 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), но в этом случае вы можете просто записать код временного файла.

    rerun2 ( on github ) – это 10-строчный скрипт Bash формы:

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

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

     rerun COMMAND 

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

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

    • Он использует inotify, поэтому он более отзывчив, чем опрос. Потрясающе для запуска субмиллисекундных модульных тестов или рендеринга графических точечных файлов каждый раз, когда вы нажимаете «сохранить».
    • Поскольку это так быстро, вам не нужно беспокоиться о том, чтобы он игнорировал большие поддиры (например, 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-limit-reached
    • Он не выполняет команды, содержащие псевдонимы Bash. Я мог бы поклясться, что это сработало. В принципе, поскольку это Bash, не выполняющий COMMAND в подоболочке, я бы ожидал, что это сработает. Я бы хотел услышать, если кто-нибудь знает, почему это не так. Многие другие решения на этой странице также не могут выполнять такие команды.
    • Лично мне жаль, что я не смог нажать ключ в терминале, в котором он работает, чтобы вручную вызвать дополнительное выполнение COMMAND. Могу я добавить это как-то просто? Одновременно выполняется цикл while while read -n1, который также вызывает выполнение?
    • Прямо сейчас я закодировал его, чтобы очистить терминал и распечатать выполненную команду на каждой итерации. Некоторым людям может нравиться добавлять флаги командной строки, чтобы отключить такие вещи и т. Д. Но это увеличит размер и сложность многократно.

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

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

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

    1. устанавливать

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

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

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

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

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

    Под Linux:

     man watch watch -n 2 your_command_to_run 

    Выполняет команду каждые 2 секунды.

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

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

    Эта версия запускает inotifywait один раз и отслеживает события (.eg: 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 

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

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

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

    Swarminglogic написал сценарий под названием watchfile.sh , также доступный как GitHub Gist .

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

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

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

     /usr/ports/sysutils/wait_on 

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

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

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

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

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

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

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

    Просто написал оболочку командной строки для него 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: Только что у Watchdog есть официальный CLI, называемый watchmedo , так что проверьте это также.

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

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

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

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

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

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

    Для тех, кто не может установить 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} 

    Ответ 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 мой SwitchTabsOnHover=false

    Я использую этот скрипт для этого. Я использую 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 при каждой записи.

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

    Если вы хотите что-то сделать после написания определенного файла, вот как это сделать:

     while true; do inotifywait -e modify /path/to/file # Do something *after* a write occurs, eg copy the file /bin/cp /path/to/file /new/path done 

    Сохраните это как, например, copy_myfile.sh и поместите файл .sh в папку /etc/init.d/ чтобы он запускался при запуске.

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

    ОБНОВЛЕНИЕ: этот ответ не удаленно, как мой другой ответ, выполните поиск на этой странице для 'rerun2'.

    Установить с помощью:

     pip install rerun 

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

     rerun "COMMAND" 

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

    По умолчанию он просматривает все файлы в текущем каталоге или под ним, пропуская такие вещи, как известные источники управления dirs, .git, .svn и т. Д.

    Необязательные флаги включают «-i NAME», который игнорирует изменения в названных файлах или каталогах. Это может быть задано несколько раз.

    Поскольку это скрипт Python, он должен выполнить команду как подпроцесс, и мы используем новый экземпляр текущей оболочки пользователя для интерпретации «КОМАНДЫ» и решаем, какой процесс фактически запускать. Однако, если ваша команда содержит псевдонимы оболочки и т. Д., Которые определены в .bashrc, они не будут загружаться подоболочкой. Чтобы исправить это, вы можете повторно запустить флаг «-I», чтобы использовать интерактивные (aka «login») подоболочки. Это медленнее и подвержено большей ошибке, чем запуск обычной оболочки, потому что она должна быть источником вашего .bashrc.

    Я использую его с Python 3, но последний раз, когда я проверил повтор, все еще работал с Python 2.

    Двусторонний меч – это то, что он использует опрос вместо inotify. С другой стороны, это означает, что он работает на каждой ОС. Кроме того, это лучше, чем некоторые другие решения, показанные здесь, только в том случае, если только одна команда выполняла заданную команду для нескольких изменений файловой системы, а не один раз для модифицированного файла, и в то же время она запускает команду второй раз, если какие-либо файлы снова меняются Пока команда запущена.

    С другой стороны, опрос означает, что существует латентность от 0.0 до 1.0 секунд, и, конечно, медленно отслеживать чрезвычайно большие каталоги. Сказав это, я никогда не сталкивался с проектом достаточно большим, чтобы это было даже заметно, если вы используете «-i», чтобы игнорировать такие большие вещи, как ваши virtualenv и node_modules.

    Хммм. rerun был незаменим для меня в течение многих лет – я в основном использую его восемь часов каждый день для запуска тестов, восстановления файлов точек, когда я их редактирую, и т. Д. Но теперь я пришел, чтобы напечатать это здесь, ясно, что мне нужно переключиться на Решение, использующее inotify (я больше не использую Windows или OSX.) И написан в Bash (поэтому он работает с псевдонимами без каких-либо дополнительных действий).

    Давайте будем гением компьютера.