Bash 基于热重载的实现

Bash-based Hot Reload Implementation

tl;dr 版本 问题:如何制作 bash 脚本/命令来监听文件的更改,然后输出特定 Bash 命令的结果?

长版 真实场景:我正在重构一个 Perl 模块(my_module.pm)并且我有一个与该模块关联的测试文件(my_module.t).我想将控制台放在一个屏幕上,每当我保存 pm 文件(在另一个屏幕上使用编辑器)时,控制台都会 运行 prove -v my_module.t.

背景:我拥有当前目录的所有权限,如果需要我可以提升到sudo。我不介意实现是否类似于 setInterval,因为它仅用于开发目的。只要我有办法 clearInterval 并且脚本不会在文件未更改时产生永无止境的输出,那就很好 :)

示例场景: 假设 bash 脚本被命名为 hot 并且它 运行s ls -l source.txt 只要给定的文件被更改。 因此,当我 运行 hot source.txt 时,脚本可能会或可能不会 运行 ls .... 一次。然后当我修改 source.txt 时,控制台 运行ning hot 将再次 运行 ls 我应该会看到新的文件大小(以及其他信息) source.txt。 如果我运行hot something.txt,修改source.txt时,应该不会运行ls。即使 source.txt 没有被修改,脚本也应该在我修改 something.txt.

时触发 ls

我想这可以通过 while 循环实现,但我很难跟踪文件更改(并且最好以间隔进行跟踪,以减少资源占用)。任何帮助将不胜感激!

使用 inotifywait 监视文件的更改事件和 运行 对其修改的测试。

inotifywait -q -m -e close_write my_module.pm |
while read -r filename event; do
  prove -v my_module.t
done

标志的用法如下。你的事件 -e 标志是 close_write 这意味着文件在最近打开写入后已关闭。

-q, --quiet

If specified once, the program will be less verbose. Specifically, it will not state when 
it has completed establishing all inotify watches. If specified twice, the 
program will output nothing at all, except in the case of fatal errors.

-m, --monitor

Instead of exiting after receiving a single event, execute indefinitely. The default 
behaviour is to exit after the first event occurs.

-e <event>, --event <event>

Listen for specific event(s) only.

close_write

A watched file or a file within a watched directory was closed, after being opened in 
writeable mode. This does not necessarily imply the file was written to.

我最终为我的~/.bashrc想出了这个功能:

function hot {
  if (( $# < 2 )); then
    echo 'USAGE: hot <command> <file1> [<file2> ... <fileN>]'
    echo '<command> will be run once when any of the files listed is changed (i.e. ls -l <file> has its output changed)'
  else
    script=
    shift
    a='';
    while true; do
      b=`ls -l $*`
      [[ $a != $b ]] && a=$b && eval $script;
      sleep .5;
    done
  fi
}

所以我可以做 hot 'prove my_module.t' my_module.pm,并且如示例中所述,我也可以做 hot 'ls -l source.txt' source.txt

实际上,一旦文件或测试文件被更改,我希望测试成为 运行。因此我会 hot 'prove my_module.t' my_module.pm my_module.t.

[[ $a != $b ]] && a=$b && eval $script; 是为了避免将自己与 nested-if 混淆 - 这是 "short form" 做 a=$b; eval $script if $a != $b.

希望这可以帮助其他人寻找答案。 :)