2010-08-27 20:02:40 +0000 2010-08-27 20:02:40 +0000
459
459

如何在文件发生变化时执行命令?

我想要一个快速简单的方法,在文件发生变化时执行命令。

目前,我正在使用这样的方法:

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

然后我需要在编辑器上保存文件时,进入终端并按回车键。我想要的是这样的方案:

&001

&001

或者其他任何简单的解决方案。更重要的是,我想要一个能在终端上运行的东西,因为我想看到程序的输出(我想看到错误信息)。

关于答案:谢谢你的回答!所有的回答都很好。所有的回答都很好,而且每一个都采取了非常不同的方法。因为我只需要接受一个,所以我就接受我实际使用过的那个(简单、快捷、易记的),虽然我知道这不是最优雅的,但我还是接受了。

答案 (37)

434
434
434
2010-08-27 20:54:55 +0000

很简单,使用 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运行时)。第二个代码段没有这个缺陷。但是,要注意的是,它假定文件名中不包含空格。如果这是一个问题,可以使用myfile.py选项来改变输出不包含文件名:

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

无论哪种方式,都有一个限制:如果某些程序用不同的文件替换了myfile,而不是写到现有的inotifywaitinotifywait就会死掉。很多编辑器都是这样工作的。

要克服这个限制,在目录上使用&007:

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的类似物)。

179
179
179
2013-10-25 09:41:16 +0000

entr* http://entrproject.org/ )提供了一个更友好的inotify界面(也支持*BSD和Mac OS X)。

它可以很容易地指定多个文件来观看(仅受ulimit -n的限制),省去了处理文件被替换的麻烦,并且需要更少的bash语法:

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

我已经在我的整个项目源码树上使用它来运行我正在修改的代码的单元测试,它已经对我的工作流程起到了巨大的推动作用。像

这样的标志(在运行之间清除屏幕)和-c(当一个新文件被添加到监控目录时退出)增加了更多的灵活性,例如你可以做:

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

截至2018年初,它还在积极开发中,可以在Debian & Ubuntu(-d)中找到它;从作者的repo构建无论如何都是无痛的。

112
112
112
2011-06-30 13:34:28 +0000

我写了一个Python程序来做这个事情,叫做when-changed

使用方法很简单:

when-changed FILE COMMAND...

或者看多个文件:

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

FILE可以是一个目录。用-r递归观看。使用%f将文件名传递给命令。

53
53
53
2013-08-20 17:12:47 +0000

这个脚本怎么样?它使用stat命令获取文件的访问时间,并在访问时间发生变化时(每当文件被访问时)运行一条命令。

30
30
30
2010-08-27 20:12:25 +0000

使用Vim的解决方案:

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

但我不要这个解决方案,因为打字有点烦人,具体要打什么字有点难记,而且要撤销它的效果有点难(需要运行:au! BufWritePost myfile.py)。另外,这个方案会阻止Vim直到命令执行完为止。

我在这里加了这个方案只是为了完整,因为可能会对其他人有帮助。

要显示程序的输出(完全打乱了你的编辑流程,因为输出会在你的编辑器上写几秒钟,直到你按回车键为止),请删除:silent命令。

23
23
23
2012-06-09 23:51:16 +0000

如果你碰巧安装了npmnodemon可能是最容易上手的方法,尤其是在OS X上,显然没有inotify工具。它支持在文件夹变化时运行一个命令。

21
21
21
2016-03-22 14:57:03 +0000

对于像我这样不会安装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

打开另一个终端:

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

watch -d -t -g ls -lR ${DIR_TO_WATCH} && ${COMMAND}

现在第一个终端会输出1,2,3

简单的脚本示例:

&001

17
17
17
2015-09-09 21:49:14 +0000

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

在你的PATH上保存github版本为'rerun',然后用以下方法调用它:

rerun COMMAND

它在你当前目录下每次有文件系统修改事件时都会运行COMMAND(递归)。 )

  • 它使用inotify,所以比轮询更灵敏。
  • 因为它的速度非常快,你不必为了性能的原因而让它忽略大的子目录(比如节点/模块)。
  • 因为它的响应速度特别快,因为它只在启动时调用一次 inotifywait,而不是每次迭代时都要运行它,并产生昂贵的建立手表的费用。(如果你使用其他的shell的话,可能就没那么酷了)
  • 它不会丢失COMMAND执行过程中发生的事件,不像本页上的其他大多数inotify解决方案。这样做是为了避免在保存缓冲区的时候,由于Vi或Emacs所做的create-writ-move动作所引起的一系列事件,而导致可能运行缓慢的测试套件被多次执行。任何在COMMAND执行过程中发生的事件都不会被忽略–它们会导致第二个死区和后续的执行。

可能不喜欢的地方:

  • 它使用inotify,所以在Linux以外的地方无法工作。默认情况下,在我使用的不同机器上,这个数字似乎被设置为5000到8000左右,但很容易增加。参见 https://unix.stackexchange.com/questions/13751/kernel-inotify-watch-limit-reached
  • 它无法执行包含Bash别名的命令。我可以发誓,这个以前是可以的。原则上,因为这是Bash,而不是在subshell中执行COMMAND,所以我希望它能正常工作。如果有人知道为什么不行,我很想知道。这个页面上的许多其他的解决方案也不能执行这样的命令。
  • 我个人希望能在它运行的终端中点击一个键,手动执行COMMAND。我能不能简单地增加这个功能?一个同时运行的'while read -n1'循环,同时调用execute?
  • 现在我已经把它编成了清除终端并在每次迭代时打印出已执行的command。有些人可能喜欢添加命令行标志来关闭这样的东西,等等。

这是对@cychoi的回答的一个改进。

12
12
12
2010-08-27 21:23:29 +0000

这里有一个简单的Shell Bourne shell脚本:

  1. 获取两个参数:要监控的文件和一个命令(如果需要的话,需要参数)
  2. 将你要监控的文件复制到 /tmp 目录
  3. 3. 每隔两秒检查一次,看看你监控的文件是否比拷贝的文件更新
  4. 如果是更新的文件, 它会用更新的原始文件覆盖拷贝并执行命令
  5. 当你按下 Ctr-C

后,会自行清理 6. 我唯一能想到的可移植性问题是, 如果其他 Unix 没有 mktemp(1) 命令, 但在这种情况下, 你可以硬编码临时文件名。

8
8
8
2014-08-08 22:20:09 +0000

如果你安装了nodemon,那么你可以这样做:

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

在我的情况下,我在本地编辑html,当文件发生变化时,将其发送到我的远程服务器。

8
8
8
2010-08-27 20:12:59 +0000

看一下incron。它类似于cron,但使用inotify事件而不是时间。

6
6
6
2014-07-09 13:16:10 +0000

看看Guard,尤其是这个插件: https://github.com/hawx/guard-shell

你可以设置它来监视你的项目目录中的任何数量的模式,并在发生变化时执行命令。甚至很有可能有一个插件可以实现你的目的。

6
6
6
2014-01-22 14:55:46 +0000

另一个用NodeJs的解决方案, fsmonitor **:

  1. 安装****

  2. 从命令行**(例如,监控日志和 “零售",如果有一个日志文件发生变化的话,从命令行*(例如,监控日志和 "零售"。

5
5
5
2015-12-28 10:44:18 +0000

看门狗](http://pythonhosted.org/watchdog)****是一个Python项目,可能就是你要找的:

支持的平台

  • Linux 2. 6 (inotify)
  • Mac OS X (FSEvents, kqueue)
  • FreeBSD/BSD (kqueue)
  • Windows (ReadDirectoryChangesW with I/O完成端口; ReadDirectoryChangesW 工作线程)
  • OS-inotify 独立的 (轮询磁盘上的目录快照,并定期比较它们;速度很慢,不建议使用)

刚写了一个命令行封装器 * watchdog_exec ***:

运行示例 在fs事件涉及到当前目录下的文件和文件夹时,运行echo $src $dst命令,除非fs事件被修改,否则运行python $src命令。

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

使用短参数,并限制只在事件涉及 “ main*.py” 时执行:

&001


EDIT: 刚发现Watchdog有一个官方的CLI叫watchmedo,所以也可以去看看。

5
5
5
2012-07-02 16:36:51 +0000

在Linux下:

man watch

watch -n 2 your_command_to_run

&001

每隔2秒就会运行一次命令,如果你的命令运行时间超过2秒,watch会等它完成后再做一次。

4
4
4
2010-08-27 20:37:52 +0000

如果你的程序生成某种日志/输出,你可以创建一个Makefile,并为该日志/输出创建一个取决于你的脚本的规则,然后做一些类似于

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

这样的事情。

4
4
4
2014-09-15 23:24:58 +0000
4
4
4
2015-06-17 18:03:51 +0000

Gilles的回答的基础上进行了改进。

这个版本运行一次inotifywait,然后监控事件(例如: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
3
3
3
2010-08-27 20:05:59 +0000

在编程方面多一点,但你想要的是像 inotify 这样的东西。有很多语言的实现,比如jnotifypyinotify。返回的信息包括文件名、操作(创建、修改、重命名、删除)和文件路径等有用信息。

3
3
3
2012-11-11 21:48:31 +0000

对于那些正在寻找FreeBSD解决方案的人来说,以下是端口:

/usr/ports/sysutils/wait_on
3
3
3
2014-04-29 13:56:13 +0000

我喜欢while inotifywait ...; do ...; done的简单性,但是它有两个问题:

  • do ...;期间发生的文件更改会被遗漏_
  • 在递归模式下使用时速度会很慢_

因此我做了一个辅助脚本,使用了inotifywait,没有这些限制。inotifyexec

我建议你把这个脚本放在你的路径中,就像~/bin/一样。

例子:inotifyexec "echo test" -r .

3
3
3
2016-04-12 14:53:44 +0000

改进了Sebastian的解决方案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命令有已知的bug(见人):它只在-g CMD输出的终端部分的VISIBLE变化时才会做出反应。

2
2
2
2017-03-01 20:14:14 +0000

你可以试试reflex

Reflex是一个小工具,它可以在某些文件发生变化时监视目录并重新运行命令。它很适合自动运行编译/链接/测试任务,当代码发生变化时,它可以重新加载你的应用程序。

1
1
1
2017-04-05 07:38:43 +0000

我有一个GIST,用法很简单

watchfiles <cmd> <paths...>

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

1
1
1
2017-06-03 12:10:57 +0000

正如其他一些人所做的那样,我也写了一个轻量级的命令行工具来完成这个任务。它有完整的文档,经过测试和模块化。

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 标志即可)
  • 如果使用 globbing,动态地维护要监视的文件列表 (-r 打开后,将其打开)
  • 多种方式 “监视 "一个文件。
  • 修改时间(默认值)
  • 文件的哈希值
  • 实现自己的三要素(这就是ModificationTime watcher)
  • 模块化设计。如果你想让命令运行,当文件被访问时,写你自己的watcher(决定doers是否应该运行的机制)是很琐碎的。
1
1
1
2016-02-26 19:10:05 +0000

我用这个脚本来做。我在监控模式下使用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。

1
1
1
2015-09-09 19:36:04 +0000

我写了一个Python程序来做这个事情,叫做rerun

UPDATE: 这个答案是一个Python脚本,它是一个Python脚本,可以轮询变化,在某些情况下很有用。关于使用 inotify 的 Linux-only Bash 脚本,请看我的另一个答案,在本页搜索'rerun2'。因此,如图所示引用它,这样可以减少额外的转义。

默认情况下,它会观察当前目录下的所有文件,跳过已知的源码控制目录、.git、.svn等。

因为是Python脚本,所以需要将该命令作为子进程来运行,我们用用户当前shell的新实例来解释'command'并决定实际运行的进程。但是,如果你的命令中包含了.bashrc中定义的shell别名之类的东西,这些东西将不会被子进程加载。为了解决这个问题,你可以给rerun一个’-I'标志,使用交互式(又名'登录’)子壳。

我在Python 3中使用它,但我最后一次检查rerun仍然在Python 2中工作。好的方面是,这意味着它可以在所有的操作系统上工作。另外,它比其他一些解决方案要好,因为它只对一堆文件系统的变化运行一次给定的命令,而不是对每一个修改的文件运行一次,同时,如果在命令运行过程中,有任何文件再次发生变化,它也会运行第二次。话虽如此,但我从来没有遇到过一个足够大的项目,只要你用’-i'来忽略像你的 virtualenv 和 node/modules 这样的大东西,这一点就很明显。但现在我来这里打这个字,很明显我需要换一个使用inotify的解决方案(我不再使用Windows或OSX了),而且是用Bash编写的(所以它可以用别名来工作,不需要任何额外的麻烦。

1
1
1
2015-09-01 04:53:52 +0000

我用的一个oneliner的答案,是用来跟踪文件变化的:

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

&001

如果你知道第一个日期是起始时间,你就不需要初始化BF。这里还有一个基于同样的策略,使用脚本的另一个答案。也看一看。


用法。我用这个来调试和关注~/.kde/share/config/plasma-desktop-appletsrc;不知道什么原因,我的SwitchTabsOnHover=false一直在流失。

1
1
1
2016-03-22 15:33:56 +0000

对于那些使用OS X的用户,你可以使用LaunchAgent来观察路径/文件的变化,并在变化发生时做一些事情。FYI - LaunchControl 是一个很好的应用程序,可以轻松地制作/修改/删除守护进程/代理。

0
0
0
2019-11-26 15:53:22 +0000

如果你不想安装任何新的东西,这里有一个小的shell脚本,你可以把它放在你的路径中(例如$HOME/bin下)。当提供的一个或多个文件被修改时,它就会运行一个命令。 注意它每隔一秒都会检查一次,所以不要包含太多,否则你的机器可能会变慢。如果你想让它在每一次保存时都运行,可以用:w来代替(见注释)。另外,如果我没记错的话,mac的stat叫做md5sum

的小技巧:当你想使用它的时候,你可能会想重复上一次运行的命令,但要重复。你可以使用md5快捷方式将上一条命令 “注入 "到这条命令中:

$ onchange './build' *.txt
0
0
0
2018-04-12 18:32:28 +0000

描述

这将监视一个文件的变化,并执行任何命令(包括进一步的参数)*_作为第二条语句。注意:你可以通过改变函数在每次while循环后的休眠时间来使函数更多(或更少)地被动运行。

**示例用法*

watch_file my_file.php php my_file.php

这一行将监视一个php文件my_file.php,并通过php解释器运行,每当它发生变化时,就通过&007解释器运行。

0
0
0
2018-03-19 20:30:21 +0000

基本用法

这里有一个解决方案,不需要安装更多的软件,开箱即用。我测试了一下,这在vim中也能正常工作。

你可以忽略这条命令的输出,它可能会提到类似于:

tail: ‘myfile.txt'已被替换;在新文件的结尾以下

高级用法

你可以把它和myfile.txt结合起来,返回true或false。你可以这样使用它:

timeout 5s bash -c 'tail -q –follow=name pipe 2> /dev/null | head -n 0’ && echo changed || echo timeout

讨论

myfile.txt 在引擎盖下使用 timeout。这就是你如何获得这种花哨的异步行为而不需要任何轮询。

有时这些命令会立即退出,但如果你立即运行第二次,它们就会像宣传的那样工作。在RHEL上,我可以使用:

timeout 5s sh -c ‘gio monitor pipe | head -n 0’ && echo changed || echo timeout

但我不确定这是否可以移植。

0
0
0
2019-01-28 16:51:42 +0000
while true; do
    find /path/to/watched/file -ctime 1s | xargs do-what-you-will
done

&001

find 如果文件名在上一个find -ctime 1s中被_改变了,就会打印出文件名。

0
0
0
2015-05-13 15:21:33 +0000

对于那些通过Googling查找对某一特定文件的修改的人来说,答案就简单多了(灵感来自于Gilles的答案)。

0
0
0
2019-03-21 11:33:37 +0000

我有一个稍微不同的情况。

我需要在日志文件改变大小时被通知,但不需要立即通知。而且可能是几天或几周后的事,所以我不能在命令行上使用inotify(反正那个服务器上没有安装/激活)(我不想使用nohup或类似的东西)。所以我决定在cron上运行一个bash脚本来检查

这个脚本会把被监视的文件的大小写在一个文本文件中,并在每次运行cron时检查,如果这个值发生了变化,会把最后一行邮件给我,如果发生了变化,会把最后一行邮件给我。

0
0
0
2019-11-23 23:41:53 +0000

查看 https://github.com/watchexec/watchexec

watchexec是一个简单的独立工具,它可以在检测到修改的时候监视路径并运行命令。

示例*

监视当前目录下的所有JavaScript、CSS和HTML文件以及所有子目录中的修改,当检测到修改时,运行make:

$ watchexec --exts js,css,html make

0
0
0
2017-03-20 13:52:53 +0000

fido “工具可能是满足这种需求的另一个选择。见 https://www.joedog.org/fido-home/