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


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

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

while read; do ./myfile.py ; done

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

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

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

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

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

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


357
2017-08-27 20:02


происхождения


Возможный дубликат: stackoverflow.com/questions/2972765/... (хотя вот это на тему =)) - Ciro Santilli 新疆改造中心 六四事件 法轮功
я ссылался перед дублированием кросс-сайта, и ему было отказано: S;) - Francisco Tapia
Решение Джонатана Хартли основывается на других решениях здесь и устраняет большие проблемы, которые отвечают самым высоким голосам: отсутствуют некоторые изменения и неэффективны. Пожалуйста, измените принятый ответ на него, который также поддерживается на github в github.com/tartley/rerun2 (или к другому решению без этих недостатков) - nealmcb


ответы:


Простой, используя 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).


341
2017-08-27 20:54



Я инкапсулировал все это (с большим количеством трюков bash) в простой в использовании sleep_until_modified.shскрипт, доступный по адресу: bitbucket.org/denilsonsa/small_scripts/src - Denilson Sá Maia
while sleep_until_modified.sh derivation.tex ; do latexmk -pdf derivation.tex ; done Это фантастично. Спасибо. - Rhys Ulerich
inotifywait -e delete_self кажется, хорошо работает для меня. - Kos
Это просто, но имеет две важные проблемы: события могут быть пропущены (все события в цикле) и инициализация inotifywait выполняется каждый раз, что делает это решение медленнее для больших рекурсивных папок. - Wernight
По какой-то причине while inotifywait -e close_write myfile.py; do ./myfile.py; done всегда выходит без выполнения команды (bash и zsh). Для этого мне нужно было добавить || true, например: while inotifywait -e close_write myfile.py || true; do ./myfile.py; done - ideasman42


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); построение авторского репо было безболезненным в любом случае.


120
2017-10-25 09:41



Не обрабатывает новые файлы и их модификации. - Wernight
@Wernight - с 7 мая 2014 года у нового -d флаг; он немного более длинный, но вы можете сделать while sleep 1 ; do find . -name '*.py' | entr -d ./myfile.py ; done для работы с новыми файлами. - Paul Fenney
доступно в aur aur.archlinux.org/packages/entr - Victor Häggqvist
лучший из тех, что я нашел на OS X точно. fswatch захватывает слишком много фанковых событий, и я не хочу тратить время, чтобы понять, почему - dtc
Стоит отметить, что ENTR доступно на Homebrew, поэтому brew install entr будет работать должным образом - jmarceli


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

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

when-changed FILE COMMAND...

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

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

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


100
2018-06-30 13:34



@ysangkok да, это так, в последней версии кода :) - joh
Теперь доступно из «pip install when-changed». Все еще работает красиво. Благодарю. - A. L. Flanagan
Чтобы сначала очистить экран, вы можете использовать when-changed FILE 'clear; COMMAND', - Dave James Miller
Этот ответ намного лучше, потому что я могу это сделать и в Windows. И этот парень на самом деле написал программу, чтобы получить ответ. - Wolfpack'08
Хорошая новость! when-changed теперь кросс-платформенный! Проверьте последние Выпуск 0.3.0 :) - joh


Как насчет этого скрипта? Он использует 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

45
2017-08-20 17:12



не было бы stat- изменить время, которое будет изменено, когда будет изменено «ответ файла»? - Xen2050
Будет ли запуск статистики много раз в секунду причиной многих чтений на диск? или системный вызов fstat автоматически сделает кеш этими ответами каким-то образом? Я пытаюсь написать своего рода «ворчащие часы» для компиляции моего кода c всякий раз, когда я вношу изменения - Oskenso Kashi
Это хорошо, если вы знаете имя файла, за которым следует следить заранее. Лучше было бы передать имя файла скрипту. Лучше всего было бы, если бы вы могли передавать много имен файлов (например, «mywatch * .py»). Лучше всего было бы, если бы он мог работать рекурсивно и в файлах в поддирерах, что и некоторые другие решения. - Jonathan Hartley
На всякий случай, когда кто-то задается вопросом о тяжелых чтениях, я протестировал этот сценарий в Ubuntu 17.04 со сном 0.05s и vmstat -d для просмотра доступа к диску. Кажется, Linux делает фантастическую работу по кешированию такого рода вещей: D - Oskenso Kashi
В «КОМАНДЕ» есть опечатка, я пытался исправить, но С.О. говорит: «Редактировать должно быть не менее 6 символов» - user337085


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

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

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

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

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


28
2017-08-27 20:12



Это может быть довольно неплохо в сочетании с entr (см. ниже) - просто сделайте vim прикоснитесь к фиктивному файлу, который просматривает entr, и пусть entr сделает остальное в фоновом режиме ... или tmux send-keys если вы оказались в такой среде :) - Paul Fenney
хороший! вы можете сделать макрос для своего .vimrc файл - ErichBSchulz


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


24
2018-06-09 23:51



Тем не менее, он смотрит только файлы .js и. Coffee. - zelk
Текущая версия, похоже, поддерживает любую команду, например: nodemon -x "bundle exec rspec" spec/models/model_spec.rb -w app/models -w spec/models - kek
Мне жаль, что у меня больше информации, но у osx есть метод отслеживания изменений, fsevents - ConstantineK
В OS X вы также можете использовать Запуск демонов с WatchPaths как показано в моей ссылке. - Adam Johns


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


12
2017-08-27 21:23



Опрос является единственным переносным способом, но в большинстве систем есть механизм уведомления об изменении файла (inotify на Linux, kqueue на FreeBSD, ...). У вас есть серьезная проблема с цитированием, когда вы делаете $cmd, но, к счастью, это легко устранить: cmd переменная и выполнить "$@", Ваш скрипт не подходит для мониторинга большого файла, но это может быть исправлено путем замены cp от touch -r (вам нужна только дата, а не содержимое). Портативность, -nt Тест требует bash, ksh или zsh. - Gilles


rerun2 (на 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.


12
2017-09-09 21:49



Я считаю, вы должны использовать "$@" вместо $@, чтобы правильно работать с аргументами, содержащими пробелы. Но в то же время вы используете eval, что заставляет пользователя повторного вызова быть особенно осторожным при цитировании. - Denilson Sá Maia
Спасибо Денилсону. Не могли бы вы привести пример того, где цитаты нужно делать осторожно? Я использую его последние 24 часа и до сих пор не видел проблем с пробелами, ни внимательно цитирует что-либо - просто вызывается как rerun 'command', Вы просто говорите, что если бы я использовал «$ @», тогда пользователь мог бы ссылаться как rerun command (без кавычек?) Это не кажется мне полезным: я вообще не хочу, чтобы Бэш делал Любые обработка команды перед передачей ее для повторного запуска. например если команда содержит «echo $ myvar», тогда я хочу увидеть новые значения myvar на каждой итерации. - Jonathan Hartley
Что-то вроде rerun foo "Some File" может сломаться. Но поскольку вы используете eval, его можно переписать как rerun 'foo "Some File", Обратите внимание, что иногда расширение пути может вводить пробелы: rerun touch *.foo скорее всего, сломается, и использование rerun 'touch *.foo' имеет несколько другую семантику (расширение пути происходит только один раз или несколько раз). - Denilson Sá Maia
Спасибо за помощь. Ага: rerun ls "some file" ломается из-за пробелов. rerun touch *.foo* обычно работает нормально, но не удается, если имена файлов, которые соответствуют * .foo, содержат пробелы. Спасибо, что помогли мне понять, как rerun 'touch *.foo' имеет разную семантику, но я подозреваю, что версия с одинарными кавычками - это семантика, которую я хочу: я хочу, чтобы каждая итерация повторного воспроизведения действовала так, как будто я набрал команду снова - следовательно, я хотеть  *.foo для каждой итерации. Я попробую ваши предложения изучить их эффекты ... - Jonathan Hartley
Больше обсуждения этого PR (github.com/tartley/rerun2/pull/1) и другие. - Jonathan Hartley


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


8
2017-08-27 20:12



Это может быть сделано для работы, но создание входа incron довольно трудоемкий процесс по сравнению с другими решениями на этой странице. - Jonathan Hartley


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

8
2018-03-22 14:57



Хороший взлом. Я тестировал и, похоже, проблема, когда список длинный, и измененный файл выходит за пределы экрана. Небольшая модификация может быть примерно такой: watch -d -t -g "ls -lR tmp | sha1sum" - Atle
если вы будете следить за своим решением каждую секунду, он работает вечно и запускает MY_COMMAND только в случае изменения какого-либо файла: watch -n1 "watch -d -t -g ls -lR && MY_COMMAND" - mnesarco
Моя версия часов (On Linux, watch from procps-ng 3.3.10) принимает поплавковые секунды за свой интервал, следовательно watch -n0.2 ... будет опросить каждую пятую часть секунды. Хорошо для этих здоровых субмиллисекундных модульных тестов. - Jonathan Hartley