Как сделать на python программу которая отслеживает запускаемые приложения
pdb — это встроенный отладчик для Python, который, в отличие от print() , позволяет отлаживать программу в процессе её работы.
Отладка Python-кода с помощью print
Как уже говорилось ранее, кто-то используют print() для отображения информации, которая помогает понять, что происходит в коде. Кто-то использует логи для тех же целей, но давайте не путать использование логов на продакшене и случаи, когда их используют во время поиска багов в коде и после удаляют.
Команды Python-отладчика
Если вы работаете с Python, то можете не только просматривать код во время отладки, но даже запускать код из командной строки или изменять значения переменных на лету.
Revolut , Moscow, можно удалённо , По итогам собеседования
Python есть встроенный отладчик под названием pdb. Это простая консольная утилита, которая обладает основной функциональностью для отладки кода. Но если вы ищете что-то более продвинутое, то стоит обратить внимание на ipdb – отладчик с функциональностью из IPython.
Проще всего вызвать отладчик pdb из кода, где вы работаете:
Как только интерпретатор доберётся до этой строчки, запустится отладчик и в консоли будут доступны новые команды.
Эта команда покажет часть кода, на выполнении которой сейчас находится интерпретатор. Можно передать два аргумента first и last для просмотра определённого участка кода. Если указать только first, то будет выведен код вокруг искомой строки.
up(p) и down(d)
Эти команды используются для передвижения по стеку вызовов. С их помощью можно отследить, откуда была вызвана текущая функция.
step() и next()
Другая пара не менее важных команд. С их помощью можно выполнять код построчно. Единственное различие между ними в том, что next() перейдёт к следующей строке вне зависимости от вызываемых функций, а step() перейдёт в вызванную функцию, если это возможно.
break()
Эта команда позволяет создавать брейкпоинты без внесений изменений в код. Ниже разберём этот этап более детально.
Краткий список команд отладчика pdb:
- args() — выводит аргументы функции;
- continue() или (cont) — продолжит выполнение до первого брейкпоинта или до завершения программы;
- help() — выводит список доступных команд или подсказки по определённой команде;
- jump() — перепрыгивает к выполнению указанной строчки кода;
- list() — выводит исходный код программы вокруг выбранной строки;
- expression() — выводит значение выражения;
- pp — выводит значение в «красивом» виде;
- quit или exit() — отменяет выполнение программы;
- return() — завершает выполнение текущей функции.
Продолжаем изучать Python-отладчик
Рассмотренный ранее способ работы с отладчиком требовал внесения изменения в код для вывода чего-нибудь или установки брейкпоинта. Но часто при работе с внешними библиотеками появляется необходимость в их отладке. Конечно, можно открыть исходный код библиотеки и вызвать pdb .
Но теперь есть возможность запускать приложение напрямую из отладчика без внесения изменения в код. Для этого воспользуемся следующей командой:
Как можно заметить, не нужно указывать полный путь до библиотеки. Можно указать относительную ссылку от sys.path . Таким же образом можно отлаживать и ваше приложение.
Теперь куда проще отлаживать код. Не надо вносить изменения в приложение или во внешние библиотеки.
Но что делать, если в приложении происходит много вызовов, а вам надо обработать только какой-то определённый? Можно точно указать условие, при выполнении которого отладчик будет прерывать выполнение приложения.
В данном примере прерывание произойдёт только в случае, если json будет иметь в себе ключ time_entry .
Отладка кода Django
Если вы используете Django, то скорее всего знаете, что, если в настройках значение параметра DEBUG установлено как True , то для каждого исключения будет выводиться отдельная страница с указанием типа исключения, стек вызовов, локальные переменные и т.д.
Если вы хотите прокачать отладчик, то установите django-extensions и используйте команду runserver_plus для запуска сервера. Также можно указать пароль для доступа к отладке следующей командой:
Прим. перев. В Werkzeug, начиная с версии 0.11, появилась возможность доступа по паролю к отладчику. Это сделано для повышения безопасности при попытках несанкционированного доступа.
Если вы используете django-extensions, то получите страницу со всеми вызовами, кодом и окном отладчика.
Процесс отладки осуществляется с помощью WSGI библиотеки Werkzeug.
Существует множество способов отладки приложений, но специализированные инструменты могут дать вам огромное преимущество по сравнению с другими разработчиками и помогут сэкономить время и силы при поиске багов. Cреды разработки предлагают широкий выбор средств отладки, подробнее о них в нашей подборке лучших IDE и редакторов кода для Python.
Узнайте, как добавить мониторинг в веб-приложение Python с помощью размещенной службы мониторинга, такой как Rollbar.
Быстрый способ проверить наличие ошибок и проблем в вашей операционной Веб-приложение Python должно быть одним из многих отличные размещенные инструменты мониторинга.
Давайте научимся быстро добавлять мониторинг Rollbar в веб-приложение, чтобы визуализировать, когда наше приложение работает правильно и когда есть проблемы. В этом уроке бутылка будет использоваться в качестве пример веб-фреймворка вместе с Rollbar в качестве служба мониторинга, но вы также можете проверить список других инструментов на странице мониторинга.
Наши инструменты
Если вам нужна помощь в получении настроена среда разработки перед запуском этого кода взгляните на это руководство по настройке Python 3 и Bottle в Ubuntu 16.04 LTS.
Установка зависимостей
Создайте новую виртуальную среду для этого проекта, используя следующие команда. Я рекомендую хранить отдельный каталог для виртуальных машин под
/Envs/ , чтобы вы знали, где находятся все виртуальные объекты вашего проекта.
Активируйте virtualenv с помощью сценария оболочки activate :
Командная строка изменится после активации virtualenv:
Помните, что вам нужно активировать свой virtualenv в каждом новом терминале окно, в котором вы хотите использовать virtualenv для запуска проекта.
Теперь мы можем установить Bottle и Rollbar в активированный virtualenv.
Найдите вывод, подобный следующему, чтобы подтвердить зависимости установлены правильно.
Наши зависимости готовы к работе, поэтому теперь мы можем создавать наше веб-приложение Python.
Наше веб-приложение Python
Создайте папку для своего проекта с именем monitor-python-apps . cd в папку, а затем создайте файл с именем app.py со следующими код.
В приведенном выше коде приложения есть несколько стандартных импортированных бутылок, поэтому мы можем создать веб-приложение Bottle и обрабатывать маршруты URL.
Сохраните app.py , и мы сможем запустить наш код. Выполните app.py с помощью python команду следующим образом (убедитесь, что ваш virtualenv все еще активирован в терминал, на котором вы запускаете эту команду):
Сервер разработки Bottle должен запуститься и отобразить несколько строк вывода.
Попробуйте получить доступ к URL-адресу с путем, содержащим только буквенные символы и дефисы, например localhost: 8080/hello-world/.
Ошибка 500 очевидна для нас прямо сейчас, потому что мы тестирование приложения локально во время разработки. Однако что происходит когда приложение развернуто и пользователь получает ошибку в своем собственном веб-сайте браузер? Они, скорее всего, уйдут из-за разочарования, и вы никогда не знать, что произошло, если вы не добавите отслеживание ошибок и приложение мониторинг.
Мониторинг ошибок с помощью Rollbar
Перейдите на главную страницу Rollbar в своем браузере чтобы добавить свой инструмент в наше приложение Bottle.
Давайте изменим наш код бутылки, чтобы позволить Rollbar собирать и агрегировать ошибки, которые появляются в нашем приложении. Измените app.py , чтобы включить следующие выделенные строки.
Новый импорт из rollbar.contrib.bottle import RollbarBottleReporter это наш канал между приложением и сервером Rollbar. полоса прокрутки это библиотека, которую мы установили ранее.
Токен ROLLBAR_SECRET необходимо установить в переменной среды. Сохраните и выйдите из app.py . Выполните следующую команду в терминале, где находится ваш virtualenv активирован:
Если вы не уверены, какой у вас секретный токен, его можно найти на экран подключения Rollbar.
Обратите внимание, что я обычно храню все свои переменные среды в .env файл и используйте template.env в качестве шаблона для того, что я должен заполнить в .env . .env может быть вызван с терминала с помощью .env команда. Обязательно никогда не фиксировать ваши секретные токены в репозиторий системы управления версиями, особенно если репозиторий публичный!
После экспорта ключа ROLLBAR_SECRET в качестве переменной среды мы можем проверить, что Rollbar работает, когда мы запускаем наше приложение. Запустить его теперь с использованием python :
Вернувшись в веб-браузер, нажмите кнопку «Готово! Перейти на панель управления».
Если о событии еще не было сообщено, мы увидим такой экран ожидания один:
Убедитесь, что ваш сервер разработки Bottle запущен, и попробуйте перейти на localhost: 8080/fullstackpython123/. Ошибка сервера 500 сразу отображается на панели управления:
Мы даже получаем электронное письмо с ошибкой (которое также можно отключить, если вы не хочу получать электронные письма о каждой ошибке):
Отлично, всего несколько строк кода, теперь у нас есть отчеты для приложения Bottle. ошибки для любого пользователя, который работает с нашим приложением.
Что теперь?
Мы только что узнали, как обнаруживать и обрабатывать ошибки с помощью Rollbar в качестве размещенного платформа мониторинга на простом примере Применение бутылки. Далее вы захотите добавить мониторинг в более сложные веб-приложения, включая те, которые используют Django или Flask. Вы также можете попробуйте более продвинутые функции Rollbar, чтобы:
Есть еще много чего узнать в области веб-разработка и развертывания, так что продолжайте учиться, читая о веб-фреймворках. Вы также можете узнать больше об интеграции Rollbar с приложениями Python через их документацию по Python .
Вы наверняка видели вывод ошибок наподобие показанного ниже:
и хотели, чтобы его было немного легче понять:
Возможно, вам даже захочется визуализировать, какие строки кода выполняются и сколько раз:
Если так, статья даст необходимые инструменты, чтобы достичь цели:
- Loguru – вывод сработавших исключений;
- snoop – печать строк кода, выполняемого в функции;
- heartrate – визуализация выполнения программы в режиме реального времени.
Все, что нужно для использования этих инструментов – одна строка кода!
Loguru
Loguru – это библиотека, которая сделает общение с системой журналов в Python приятнее. Она предоставляет много интересных функций, но одна из них заслуживает отдельного внимания – возможность отлавливать неожиданные ошибки и отображать, какое значение переменной приводит к сбою кода.
Установить Loguru можно одной командой:
Чтобы понять, чем Loguru может быть вам полезна, представьте, что есть 2 функции – division и divide_numbers, при этом функция divide_numbers сейчас выполняется.
Обратите внимание, что комбинации ([2,1,0], 2) возвращают [(2, 1), (2, 0), (1, 0)]. После выполнения приведенного выше кода мы получим следующую ошибку:
Из выходных данных становится ясно, что возвращающая num1 / num2 строка является местом возникновения ошибки, но непонятно, какие значения num1 и num2 ее вызывают. Это можно легко отследить, добавив декоратор Loguru.catch:
Добавив logger.catch исключения гораздо проще понять: ошибка возникает при делении 2 на 0.
Snoop
Как быть, если в коде нет ошибки, но мы хотим выяснить, что в нем происходит? В этом случае пригодится snoop . Это пакет Python, который выводит на экран строки выполняемого кода вместе со значениями каждой переменной, добавляя только один декоратор.
Чтобы установить snoop, введите следующую команду:
Представим, что у нас есть функция под названием factorial, которая находит факториал целого числа:
Чтобы понять, почему результат factorial(5) равен 20, добавим декоратор snoop в функцию factorial.
В приведенном выше выводе можно просмотреть значения переменных, и какие строки кода выполняются. Теперь легче понять, как работает рекурсия.
Heartrate
Если необходимо визуализировать, какие строки и сколько раз выполняются, попробуйте heartrate.
H eartrate также создан разработчиками snoop. Чтобы его установить, введите команду:
Теперь добавим heartrate.trace(browser=True) в предыдущий код. Это откроет отображающее визуализацию файла окно браузера , в котором была вызвана функция trace():
Диаграммы подсвечивают искомые строки: более длинные полосы означают больше попаданий, а светлые цвета – более свежее изменение.
На приведенной выше иллюстрации мы видим, как выполняется код:
- if x==1 (5 раз)
- return 1 (1 раз)
- return (x * factorial(x-1)) (4 раза)
Вывод имеет смысл, так как начальное значение x равно 5, и функция вызывается повторно до тех пор, пока x не станет равным 1.
Теперь посмотрим, как визуализировать выполнение в режиме реального времени с помощью heartrate . Добавим sleep (0.5) , чтобы программа работала немного медленнее, и увеличим num до 20.
Теперь мы можем в режиме реального времени видеть, какие строки кода выполняются, и сколько раз отработала каждая из них.
Заключение
Мы изучили три инструмента для отслеживания и визуализации выполнения кода в Python. Надеемся, что с ними отладка станет для вас менее болезненной. Поскольку эти инструменты требуют только одной строки кода, почему бы не попробовать их в работе? Удачи в обучении!
В скриптах, написанных для автоматизации определенных задач, нам часто требуется запускать внешние программы и контролировать их выполнение. При работе с Python мы можем использовать модуль subprocess для создания подобных скриптов. Этот модуль является частью стандартной библиотеки языка. В данном руководстве мы кратко рассмотрим subprocess и изучим основы его использования.
Прочитав статью, вы узнаете как:
- Использовать функцию run для запуска внешнего процесса.
- Получить стандартный вывод процесса и информацию об ошибках.
- Проверить код возврата процесса и вызвать исключение в случае сбоя.
- Запустить процесс, используя оболочку в качестве посредника.
- Установить время ожидания завершения процесса.
- Использовать класс Popen напрямую для создания конвейера ( pipe ) между двумя процессами.
Так как модуль subprocess почти всегда используют с Linux все примеры будут касаться Ubuntu. Для пользователей Windows советую скачать терминал Ubuntu 18.04 LTS.
Функция «run»
Функция run была добавлена в модуль subprocess только в относительно последних версиях Python (3.5). Теперь ее использование является рекомендуемым способом создания процессов и должно решать наиболее распространенные задачи. Прежде всего, давайте посмотрим на простейший случай применения функции run .
Предположим, мы хотим запустить команду ls -al ; для этого в оболочке Python нам нужно ввести следующие инструкции:
Вывод внешней команды ls отображается на экране:
Здесь мы просто использовали первый обязательный аргумент функции run , который может быть последовательностью, «описывающей» команду и ее аргументы (как в примере), или строкой, которая должна использоваться при запуске с аргументом shell=True (мы рассмотрим последний случай позже).
Захват вывода команды: stdout и stderr
Что, если мы не хотим, чтобы вывод процесса отображался на экране. Вместо этого, нужно чтобы он сохранялся: на него можно было ссылаться после выхода из процесса? В этом случае нам стоит установить для аргумента функции capture_output значение True :
Как мы можем впоследствии получить вывод ( stdout и stderr ) процесса? Если вы посмотрите на приведенные выше примеры, то увидите, что мы использовали переменную process для ссылки на объект CompletedProcess , возвращаемый функцией run . Этот объект представляет процесс, запущенный функцией, и имеет много полезных свойств. Помимо прочих, stdout и stderr используются для «хранения» соответствующих дескрипторов команды, если, как уже было сказано, для аргумента capture_output установлено значение True . В этом случае, чтобы получить stdout , мы должны использовать:
По умолчанию stdout и stderr представляют собой последовательности байтов. Если мы хотим, чтобы они хранились в виде строк, мы должны установить для аргумента text функции run значение True .
Управление сбоями процесса
Команда, которую мы запускали в предыдущих примерах, была выполнена без ошибок. Однако при написании программы следует принимать во внимание все случаи. Так, что случится, если порожденный процесс даст сбой? По умолчанию ничего «особенного» не происходит. Давайте посмотрим на примере: мы снова запускаем команду ls , пытаясь вывести список содержимого каталога /root, который не доступен для чтения обычным пользователям:
Мы можем узнать, не завершился ли запущенный процесс ошибкой, проверив его код возврата, который хранится в свойстве returncode объекта CompletedProcess :
Видите? В этом случае returncode равен 2 , подтверждая, что процесс столкнулся с ошибкой, связанной с недостаточными правами доступа, и не был успешно завершен. Мы могли бы проверять выходные данные процесса таким образом чтобы при возникновении сбоя возникало исключение. Используйте аргумент check функции run : если для него установлено значение True , то в случае, когда внешний процесс завершается ошибкой, возникает исключение CalledProcessError :
Обработка исключений в Python довольно проста. Поэтому для управления сбоями процесса мы могли бы написать что-то вроде:
Исключение CalledProcessError , как мы уже сказали, возникает, когда код возврата процесса не является 0 . У данного объекта есть такие свойства, как returncode , cmd , stdout , stderr ; то, что они представляют, довольно очевидно. Например, в приведенном выше примере мы просто использовали свойство cmd , чтобы отобразить последовательность, которая использовалась для запуска команды при возникновении исключения.
Выполнение процесса в оболочке
Процессы, запущенные с помощью функции run , выполняются «напрямую», это означает, что для их запуска не используется оболочка: поэтому для процесса не доступны никакие переменные среды и не выполняются раскрытие и подстановка выражений. Давайте посмотрим на пример, который включает использование переменной $HOME :
Как видите, переменная $HOME не была заменена на соответствующее значение. Такой способ выполнения процессов является рекомендованным, так как позволяет избежать потенциальные угрозы безопасности. Однако, в некоторых случаях, когда нам нужно вызвать оболочку в качестве промежуточного процесса, достаточно установить для параметра shell функции run значение True . В таких случаях желательно указать команду и ее аргументы в виде строки:
Все переменные, существующие в пользовательской среде, могут использоваться при вызове оболочки в качестве промежуточного процесса. Хотя это может показаться удобным, такой подход является источником проблем. Особенно при работе с потенциально опасным вводом, который может привести к внедрению вредоносного shell-кода. Поэтому запуск процесса с shell=True не рекомендуется и должен использоваться только в безопасных случаях.
Ограничение времени работы процесса
Обычно мы не хотим, чтобы некорректно работающие процессы бесконечно исполнялись в нашей системе после их запуска. Если мы используем параметр timeout функции run , то можем указать количество времени в секундах, в течение которого процесс должен завершиться. Если он не будет завершен за это время, процесс будет остановлен сигналом SIGKILL. Который, как мы знаем, не может быть перехвачен. Давайте продемонстрируем это, запустив длительный процесс и предоставив timeout в секундах:
В приведенном выше примере мы запустили команду ping без указания фиксированного числа пакетов ECHO REQUEST, поэтому она потенциально может работать вечно. Мы также установили время ожидания в 5 секунд с помощью параметра timeout . Как мы видим, ping была запущена, а по истечении 5 секунд возникло исключение TimeoutExpired и процесс был остановлен.
Функции call, check_output и check_call
Как мы уже говорили ранее, функция run является рекомендуемым способом запуска внешнего процесса. Она должна использоваться в большинстве случаев. До того, как она была представлена в Python 3.5, тремя основными функциями API высокого уровня, применяемыми для создания процессов, были call , check_output и check_call ; давайте взглянем на них вкратце.
Прежде всего, функция call : она используется для выполнения команды, описанной параметром args; она ожидает завершения команды; ее результатом является соответствующий код возврата. Это примерно соответствует базовому использованию функции run.
Поведение функции check_call практически не отличается от run , когда для параметра check задано значение True : она запускает указанную команду и ожидает ее завершения. Если код возврата не равен 0 , возникает исключение CalledProcessError .
Наконец, функция check_output . Она работает аналогично check_call , но возвращает вывод запущенной программы, то есть он не отображается при выполнении функции.
Работа на более низком уровне с классом Popen
До сих пор мы изучали функции API высокого уровня в модуле subprocess, особенно run . Все они под капотом используют класс Popen . Из-за этого в подавляющем большинстве случаев нам не нужно взаимодействовать с ним напрямую. Однако, когда требуется большая гибкость, без создания объектов Popen не обойтись.
Предположим, например, что мы хотим соединить два процесса, воссоздав поведение конвейера (pipe) оболочки. Как мы знаем, когда передаем две команды в оболочку, стандартный вывод той, что находится слева от пайпа «|», используется как стандартный ввод той, которая находится справа. В приведенном ниже примере результат выполнения двух связанных конвейером команд сохраняется в переменной:
Чтобы воссоздать подобное поведение с помощью модуля subprocess без установки параметра shell в значение True , как мы видели ранее, мы должны напрямую использовать класс Popen :
Прочитав эту статью, вы научитесь обнаруживать изменения в существующих файлах приложения Python. Для этого возьмем хорошо поддерживаемый модуль под названием watchdog .
Согласно официальной документации, watchdog — это библиотека API Python и утилиты оболочки для мониторинга событий файловой системы.
Он поддерживает как Python 2.7, так и 3.4+. Однако с более старыми версиями рекомендуется применять watchdog < 0.10.0. В этой статье рассмотрим только библиотеку API Python. Приступаем к установке необходимых модулей.
Установка
pip install делает установку простой и понятной. Прежде чем продолжать, настоятельно рекомендуем настроить виртуальную среду. Существует два доступных метода установки.
Установка из PyPI
Выполните в терминале команду:
Она установит последнюю версию из PyPI (на момент написания статьи — 0.10.2).
Установка из репозитория
Вы также можете клонировать репозиторий в локальной папке и установить его содержимое в обычном режиме. Клонируем репозиторий с помощью такой команды:
Измените рабочий каталог с помощью следующей команды. Убедитесь, что в рабочем каталоге содержится файл с именем setup.py .
Проведите установку, выполнив команду:
Одно из основных преимуществ клонирования непосредственно из репозитория — так вы получите последнюю версию с дополнительными функциями.
Можно проверить установку, выполнив команду:
Теперь перейдем к следующему разделу и начнем писать код на Python.
Реализация
Основные строительные блоки watchdog основаны на следующих классах.
- Наблюдатель ( Observer ).
- Обработчик событий ( Event handler ).
Следовательно, реализация довольно проста и состоит в следующем.
- Создайте экземпляр класса потоков watchdog.observers.Observer .
- Определите подкласс обработчика событий с вашей собственной реализацией и создайте из него экземпляр.
- Вызовите функцию расписания через экземпляр наблюдателя, прикрепляя обработчик событий. Функция принимает несколько других входных параметров, таких как путь к отслеживаемому каталогу.
- Запустите поток наблюдателя и дождитесь, пока он сгенерирует события, которые вызовут исполнение кода внутри обработчика событий.
Обработчик событий
В настоящее время в модуле доступно 4 типа обработчиков событий.
- FileSystemEventHandler — базовый обработчик событий файловой системы, из которого можно переопределить методы.
- PatternMatchingEventHandler сопоставляет заданные шаблоны с путями к файлам, которые связаны с происходящими событиями.
- RegexMatchingEventHandler сопоставляет заданные регулярные выражения с путями к файлам, которые связаны с происходящими событиями.
- LoggingEventHandler регистрирует все записанные события.
Остальные классы наследуются от FileSystemEventHandler , который предоставляет для переопределения следующие функции.
- on_any_event — обработчик событий Catch-all.
- on_created вызывается при создании файла или каталога.
- on_deleted вызывается при удалении файла или каталога.
- on_modified вызывается при изменении файла или каталога.
- on_moved вызывается при перемещении или переименовании файла или каталога.
Импорт
Создайте новый файл Python и добавьте в него объявление импорта. Назовем его test.py .
Подкласс FileSystemEventHandler
Создайте новый класс, который наследуется от FileSystemEventHandler , и перепишите функции в зависимости от ваших вариантов использования. Назовем его MyHandler , но вы можете выбрать любое удобное для вас имя.
Замените операторы вывода логикой вашей реализации. Для каждой из функций будет представлен входной параметр event , который содержит следующие переменные.
- event_type — тип события в виде строки. По умолчанию значение None .
- is_directory — true , если событие было выдано для каталога. В противном случае — false .
- src_path — исходный путь объекта файловой системы, вызвавшего это событие.
Наиболее полезный параметр здесь — src_path . Им можно воспользоваться, чтобы определить, какой файл был изменен, прежде чем запускать соответствующую логику.
Наблюдатель и обработчик событий
После добавления подкласса можно безопасно создать его экземпляр вместе с классом Observer . Назначьте выбранный путь для процесса отслеживания. В данном случае проверим недавно созданную папку под названием json .
Вы также можете установить рекурсивный параметр, но настоятельно рекомендуется предварительно определить иерархию и установить для нее значение false , чтобы предотвратить проблемы с недостатком разрешений или доступом к подпапке.
Вызов start запустит поток, и при внесении изменений в соответствующий путь будет сгенерировано событие.
Чтобы протестировать написанный код, надо реализовать цикл выполнения и предотвратить выход из него. На выходе при возникновении исключения KeyboardInterrupt вызовите функцию stop для очистки ресурсов.
Сохраните файл Python и запустите его из терминала. Измените имя в соответствии с тем, которое задали раньше.
Протестировать все это легко: создайте новый документ, измените его содержимое и удалите из каталога. Вот пример вывода:
Обратите внимание: поведение и результат могут отличаться в зависимости от редактора. Например, у Vim есть известная проблема с событиями типа modified .
“Vim не изменяет файлы без соответствующего указания. Он создает резервные копии файлов, а затем меняет их местами, чтобы заменить файлы, которые вы редактируете на диске. Это означает, что если вы пользуетесь Vim для редактирования файлов, события при изменении этих файлов не будут запускаться watchdog . Возможно, вам потребуется соответствующим образом настроить Vim, чтобы отключить эту функцию”.
Читайте также: