每当文件更改时如何执行命令?

Den*_*aia 549 linux unix script shell

我想要一种快速而简单的方法来在文件更改时执行命令。我想要一些非常简单的东西,我会在终端上运行并在我完成处理该文件时关闭它。

目前,我正在使用这个:

while read; do ./myfile.py ; done
Run Code Online (Sandbox Code Playgroud)

然后Enter,每当我将该文件保存在我的编辑器中时,我都需要转到该终端并按。我想要的是这样的:

while sleep_until_file_has_changed myfile.py ; do ./myfile.py ; done
Run Code Online (Sandbox Code Playgroud)

或任何其他简单的解决方案。

顺便说一句:我正在使用 Vim,我知道我可以添加一个自动命令来在 BufWrite 上运行一些东西,但这不是我现在想要的那种解决方案。

更新:如果可能的话,我想要一些简单的、可丢弃的。更重要的是,我想在终端中运行一些东西,因为我想查看程序输出(我想查看错误消息)。

关于答案:感谢您的所有回答!他们都非常好,每个人都采用与其他人截然不同的方法。因为我只需要接受一个,所以我接受了我实际使用过的一个(它简单、快速且易于记忆),尽管我知道它不是最优雅的。

Gil*_*il' 501

简单,使用inotifywait(安装您的发行版的inotify-tools包):

while inotifywait -e close_write myfile.py; do ./myfile.py; done
Run Code Online (Sandbox Code Playgroud)

或者

inotifywait -q -m -e close_write myfile.py |
while read -r filename event; do
  ./myfile.py         # or "./$filename"
done
Run Code Online (Sandbox Code Playgroud)

第一个片段更简单,但它有一个明显的缺点:它会错过inotifywait未运行时(尤其myfile是运行时)执行的更改。第二个片段没有这个缺陷。但是,请注意它假定文件名不包含空格。如果这是一个问题,请使用该--format选项将输出更改为不包含文件名:

inotifywait -q -m -e close_write --format %e myfile.py |
while read events; do
  ./myfile.py
done
Run Code Online (Sandbox Code Playgroud)

无论哪种方式,都有一个限制:如果某个程序替换myfile.py为不同的文件,而不是写入现有的myfileinotifywait则会死亡。许多编辑都是这样工作的。

要克服此限制,请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
Run Code Online (Sandbox Code Playgroud)

或者,使用另一个使用相同底层功能的工具,例如incron(允许您在修改文件时注册事件)或fswatch(一个也适用于许多其他 Unix 变体的工具,使用每个变体的 Linux inotify 模拟)。

  • 我已经将所有这些(使用相当多的 bash 技巧)封装在一个简单易用的 `sleep_until_modified.sh` 脚本中,可从以下网址获得:http://bitbucket.org/denilsonsa/small_scripts/src (51认同)
  • `而 sleep_until_modified.sh derivation.tex ; 做 latexmk -pdf derivation.tex ;done` 太棒了。谢谢你。 (14认同)
  • 出于某种原因`while inotifywait -e close_write myfile.py; 做./myfile.py; done` 总是在不运行命令(bash 和 zsh)的情况下退出。为此,我需要添加`|| true`,例如:`while inotifywait -e close_write myfile.py || 真的; 做./myfile.py; 完成` (9认同)
  • `inotifywait -e delete_self` 似乎对我来说效果很好。 (6认同)
  • 这很简单,但有两个重要问题:事件可能会被遗漏(循环中的所有事件)和 inotifywait 的初始化每次都完成,这使得该解决方案对于大型递归文件夹更慢。 (3认同)
  • 更新:第一条评论中提到的网址已经移动:https://github.com/denilsonsa/small_scripts/blob/master/sleep_until_modified.sh (2认同)

Pau*_*ney 223

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

它使得指定多个要监视的文件变得非常容易(仅受 限制ulimit -n),消除了处理被替换文件的麻烦,并且需要更少的 bash 语法:

$ find . -name '*.py' | entr ./myfile.py
Run Code Online (Sandbox Code Playgroud)

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

-c(在运行之间清除屏幕)和-d(在将新文件添加到受监视目录时退出)之类的标志增加了更多的灵活性,例如您可以执行以下操作:

$ while sleep 1 ; do find . -name '*.py' | entr -d ./myfile.py ; done
Run Code Online (Sandbox Code Playgroud)

截至 2018 年初,它仍在积极开发中,可以在 Debian 和 Ubuntu ( apt install entr) 中找到;在任何情况下,从作者的 repo 构建都是无痛的。

  • 值得注意的是 **entr** 在 Homebrew 上可用,因此 `brew install entr` 将按预期工作 (7认同)
  • 我肯定在 OS X 上找到的最好的。fswatch 抓住了太多时髦的事件,我不想花时间找出原因 (6认同)
  • 不处理新文件及其修改。 (4认同)
  • @Wernight - 截至 2014 年 5 月 7 日,entr 具有新的 `-d` 标志;它有点啰嗦,但你可以做 `while sleep 1 ; 确实找到。-name '*.py' | entr -d ./myfile.py ; done` 来处理新文件。 (2认同)
  • 至少在 debian jessie/8.2 上的 debian repos 中也可以使用 entr... (2认同)

joh*_*joh 132

我写了一个 Python 程序来完成这个叫做when-changed 的事情。

用法很简单:

when-changed FILE COMMAND...
Run Code Online (Sandbox Code Playgroud)

或观看多个文件:

when-changed FILE [FILE ...] -c COMMAND
Run Code Online (Sandbox Code Playgroud)

FILE可以是目录。用 递归观察-r。使用%f的文件名传递给此命令。

  • 现在可以从“pip install when-changed”获得。仍然很好用。谢谢。 (5认同)
  • 大家好消息!`when-changed` 现在是跨平台的!查看最新的 [0.3.0 版本](https://github.com/joh/when-changed/releases/tag/v0.3.0) :) (4认同)
  • 要先清除屏幕,您可以使用 `when-changed FILE 'clear; 命令'`。 (2认同)

小智 61

这个剧本怎么样?它使用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
Run Code Online (Sandbox Code Playgroud)

  • 以防万一有人想知道重读,我在 Ubuntu 17.04 中测试了这个脚本,睡眠时间为 0.05 秒,并使用 ```vmstat -d``` 来观察磁盘访问。似乎 linux 在缓存这类东西方面做得非常出色:D (7认同)
  • `stat`-ing 修改时间不是更好的“每当文件更改时”的答案吗? (4认同)

Den*_*aia 33

使用 Vim 的解决方案:

:au BufWritePost myfile.py :silent !./myfile.py
Run Code Online (Sandbox Code Playgroud)

但我不想要这个解决方案,因为它打字有点烦人,很难记住要输入什么,确切地说,并且撤消它的效果有点困难(需要运行:au! BufWritePost myfile.py)。此外,这个解决方案会阻塞 Vim,直到命令完成执行。

我在这里添加了这个解决方案只是为了完整性,因为它可能会帮助其他人。

要显示程序输出(并完全中断您的编辑流程,因为输出将覆盖编辑器几秒钟,直到您按 Enter),请删除该:silent命令。


小智 33

对于那些不能inotify-tools像我一样安装的人,这应该很有用:

watch -d -t -g ls -lR

当输出更改时,此命令将退出,ls -lR将列出每个文件和目录及其大小和日期,因此如果文件更改,它应该退出命令,如 man 所说:

-g, --chgexit
          Exit when the output of command changes.
Run Code Online (Sandbox Code Playgroud)

我知道这个答案可能不会被任何人阅读,但我希望有人能找到它。

命令行示例:

~ $ cd /tmp
~ $ watch -d -t -g ls -lR && echo "1,2,3"
Run Code Online (Sandbox Code Playgroud)

打开另一个终端:

~ $ echo "testing" > /tmp/test
Run Code Online (Sandbox Code Playgroud)

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

简单的脚本示例:

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

watch -d -t -g ls -lR ${DIR_TO_WATCH} && ${COMMAND}
Run Code Online (Sandbox Code Playgroud)

  • 不错的黑客。我测试过,当列表很长并且更改的文件落在屏幕之外时,它似乎有问题。一个小的修改可能是这样的:`watch -d -t -g "ls -lR tmp | sha1sum"` (6认同)
  • 如果您每秒都在观看您的解决方案,它会永远工作并仅在某些文件更改时运行 MY_COMMAND: watch -n1 "watch -d -t -g ls -lR && MY_COMMAND" (3认同)
  • 我最终发现有用的命令行是: `watch -n1 "watch -t -g ls --full-time >/dev/null && MY_CMD"` 请注意 --full-time 否则时间戳仅到分钟,并将 watch-ls 输出重定向到 dev null。 (3认同)

小智 29

如果您碰巧npm安装了,nodemon这可能是最简单的入门方法,尤其是在 OS X 上,它显然没有 inotify 工具。它支持在文件夹更改时运行命令。

  • 当前版本似乎支持任何命令,例如:`nodemon -x "bundle exec rspec" spec/models/model_spec.rb -w app/models -w spec/models` (7认同)
  • 但是,它只监视 .js 和 .coffee 文件。 (5认同)

Jon*_*ley 25

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
Run Code Online (Sandbox Code Playgroud)

在 PATH 上将 github 版本另存为“重新运行”,并使用以下命令调用它:

rerun COMMAND
Run Code Online (Sandbox Code Playgroud)

每次在当前目录中发生文件系统修改事件时,它都会运行 COMMAND(递归)。

人们可能喜欢它的事情:

  • 它使用 inotify,因此比轮询响应更快。每次点击“保存”时,都非常适合运行亚毫秒单元测试或渲染 graphviz 点文件。
  • 因为它是如此之快,所以您不必为了性能原因而费心告诉它忽略大型子目录(如 node_modules)。
  • 它的响应速度非常快,因为它只在启动时调用 inotifywait 一次,而不是运行它,并且在每次迭代中都会产生建立监视的昂贵代价。
  • 它只有 12 行 Bash
  • 因为它是 Bash,它解释你传递给它的命令,就像你在 Bash 提示符下输入的一样。(如果您使用另一个外壳,这可能不太酷。)
  • 与此页面上的大多数其他 inotify 解决方案不同,它不会丢失 COMMAND 执行时发生的事件。
  • 在第一个事件中,它进入 0.15 秒的“死区”,在此期间其他事件被忽略,然后 COMMAND 只运行一次。这样一来,由 Vi 或 Emacs 在保存缓冲区时执行的创建-写入-移动舞蹈引起的一系列事件不会导致对可能运行缓慢的测试套件进行多次费力的执行。COMMAND 执行时发生的任何事件都不会被忽略——它们将导致第二个死区和后续执行。

人们可能不喜欢它的事情:

  • 它使用 inotify,所以不能在 Linuxland 之外工作。
  • 因为它使用 inotify,所以它会试图监视包含比用户 inotify 监视的最大数量更多的文件的目录。默认情况下,这似乎在我使用的不同机器上设置为 5,000 到 8,000 左右,但很容易增加。请参阅https://unix.stackexchange.com/questions/13751/kernel-inotify-watch-limit-reached
  • 它无法执行包含 Bash 别名的命令。我可以发誓这曾经奏效。原则上,因为这是 Bash,而不是在子 shell 中执行 COMMAND,所以我希望它可以工作。我很想听听如果有人知道为什么不这样做。此页面上的许多其他解决方案也无法执行此类命令。
  • 就我个人而言,我希望我能够在它运行的终端中按下一个键来手动导致额外执行 COMMAND。我可以以某种方式添加这个吗?一个同时运行的“while read -n1”循环也调用了execute?
  • 现在我已经编码它来清除终端并在每次迭代时打印执行的命令。有些人可能喜欢添加命令行标志来关闭这样的功能,等等。但这会成倍增加规模和复杂性。

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

  • 我相信您应该使用 `"$@"` 而不是 `$@`,以便正确处理包含空格的参数。但同时你使用了`eval`,这迫使重新运行的用户在引用时要格外小心。 (2认同)

小智 14

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

nodemon -w <watch directory> -x "<shell command>" -e ".html"
Run Code Online (Sandbox Code Playgroud)

就我而言,我在本地编辑 html 并在文件更改时将其发送到我的远程服务器。

nodemon -w <watch directory> -x "scp filename jaym@jay-remote.com:/var/www" -e ".html"
Run Code Online (Sandbox Code Playgroud)


小智 13

这是一个简单的 shell Bourne shell 脚本:

  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  
    
    Run Code Online (Sandbox Code Playgroud)

这适用于 FreeBSD。我能想到的唯一可移植性问题是,如果其他一些 Unix 没有 mktemp(1) 命令,但在这种情况下,您可以硬编码临时文件名。

  • 轮询是唯一可移植的方式,但大多数系统都有文件更改通知机制(Linux 上的inotify,FreeBSD 上的kqueue,...)。当你执行 `$cmd` 时,你有一个严重的引用问题,但幸运的是这很容易解决:抛弃 `cmd` 变量并执行 `"$@"`。您的脚本不适合监控大文件,但可以通过用 `touch -r` 替换 `cp` 来解决(您只需要日期,而不是内容)。在可移植性方面,`-nt` 测试需要 bash、ksh 或 zsh。 (9认同)

小智 10

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


小智 8

NodeJs 的另一个解决方案fsmonitor

  1. 安装

    sudo npm install -g fsmonitor
    
    Run Code Online (Sandbox Code Playgroud)
  2. 从命令行(例如,如果一个日志文件更改,则监视日志和“零售”)

    fsmonitor -s -p '+*.log' sh -c "clear; tail -q *.log"
    
    Run Code Online (Sandbox Code Playgroud)


小智 7

看看 Guard,特别是这个插件:

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

您可以将其设置为监视项目目录中任意数量的模式,并在发生更改时执行命令。即使有一个插件可用于您首先尝试做的事情,也很有可能。


小智 6

如果您的程序生成某种日志/输出,您可以创建一个 Makefile,其中包含该日志/输出的规则,该规则取决于您的脚本并执行类似操作

while true; do make -s my_target; sleep 1; done
Run Code Online (Sandbox Code Playgroud)

或者,您可以创建一个虚假目标并使其规则既调用您的脚本又触摸虚假目标(同时仍然取决于您的脚本)。

  • `而睡眠 1 ; 做点什么 ; done` 略好于 `while true ;做点什么 ; 睡觉 1 ; 完成`。至少在按 Ctrl+C 时它很容易停止。 (11认同)
  • @StevenLu:不,睡眠不是忙碌的等待。问题是,如果睡眠在体内,Control-C 将终止睡眠,循环将重新开始。启动循环的功耗是微不足道的。在终端中自己尝试一下。如果你的身体有睡眠,你需要按住 Control-C 才能让它工作。 (2认同)

Eri*_*ski 6

在 Linux 下:

man watch

watch -n 2 your_command_to_run
Run Code Online (Sandbox Code Playgroud)

将每 2 秒运行一次命令。

如果您的命令运行时间超过 2 秒,watch 将等到它完成后再执行。

  • 当命令运行时间超过两秒时会发生什么? (2认同)

Wer*_*ght 6

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

  • 期间发生的文件更改do ...;被错过
  • 在递归模式下使用时速度较慢

因此,我制作了一个使用inotifywait且没有这些限制的帮助程序脚本:inotifyexec

我建议你把这个脚本放在你的路径中,比如在~/bin/. 只需运行命令即可描述用法。

例子:inotifyexec "echo test" -r .


小智 6

Watchdog是一个 Python 项目,可能正是您要找的:

支持的平台

  • Linux 2.6 (inotify)
  • Mac OS X (FSEvents, kqueue)
  • FreeBSD/BSD (kqueue)
  • Windows(带有 I/O 完成端口的 ReadDirectoryChangesW;ReadDirectoryChangesW 工作线程)
  • 独立于操作系统(轮询磁盘以获取目录快照并定期比较它们;缓慢且不推荐)

刚刚为它写了一个命令行包装器watchdog_exec

示例运行

在涉及当前目录下的文件和文件夹的 fs 事件上,运行echo $src $dst命令,除非 fs 事件被修改,然后运行python $src命令。

python -m watchdog_exec . --execute echo --modified python
Run Code Online (Sandbox Code Playgroud)

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

python -m watchdog_exec . -e echo -a echo -s __main__.py
Run Code Online (Sandbox Code Playgroud)

编辑:刚刚发现 Watchdog 有一个名为 的官方 CLI watchmedo,所以也请检查一下。


Den*_*aia 5

swarminglogic写了一个脚本调用watchfile.sh,也可作为GitHub的要点

  • 这是一个功能丰富的 200 行 Bash 脚本,它在给定的文件名上轮询 `stat`,在输出上运行 `md5sum`,并在此值更改时重新运行给定的命令。因为它是 Bash,我怀疑它在运行给定命令方面做得很好,就像您在 Bash 提示符下键入它一样。(相比之下,这里用其他语言编写的大多数解决方案将无法执行命令,例如包含诸如 `ll` 之类的 shell 别名) (2认同)

cyc*_*hoi 5

改进了Gilles 的回答

此版本运行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
Run Code Online (Sandbox Code Playgroud)


小智 5

使用命令改进了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
Run Code Online (Sandbox Code Playgroud)

调用示例:

watch_cmd.sh "ls -lR /etc/nginx | grep .conf$" "sudo service nginx reload"
Run Code Online (Sandbox Code Playgroud)

它可以工作,但要小心:watch命令有已知的错误(请参阅 man):它仅对输出终端部分中可见的更改做出反应-g CMD


mas*_*ilo 5

你可以试试反射

Reflex 是一个小工具,用于监视目录并在某些文件更改时重新运行命令。它非常适合自动运行编译/lint/测试任务以及在代码更改时重新加载应用程序。

# Rerun make whenever a .c file changes
reflex -r '\.c$' make
Run Code Online (Sandbox Code Playgroud)