文件或目录更改时如何运行Shell脚本?


70

我想在特定文件或目录更改时运行Shell脚本。

我如何轻松做到这一点?


1
我有一个帖子,我认为基本上是相同的:stackoverflow.com/q/2972765/119790
Ian Vaughan

1
@ MerlynMorgan-Graham我将其移至超级用户,因为答案可能与编程没有任何关系-即,可能有一些程序或配置选项可以使用,而无需任何编程。我有同样的问题,并首先搜索了超级用户:p
Benubird

Answers:


19

使用inotify-tools

链接的Github页面上有许多示例。这是其中之一。

#!/bin/sh

cwd=$(pwd)

inotifywait -mr \
  --timefmt '%d/%m/%y %H:%M' --format '%T %w %f' \
  -e close_write /tmp/test |
while read -r date time dir file; do
       changed_abs=${dir}${file}
       changed_rel=${changed_abs#"$cwd"/}

       rsync --progress --relative -vrae 'ssh -p 22' "$changed_rel" \
           usernam@example.com:/backup/root/dir && \
       echo "At ${time} on ${date}, file $changed_abs was backed up via rsync" >&2
done

12
incron是另一种选择。
丹尼斯·威廉姆森

2
fanotify是另一种选择。建立在inotify之上。它对inotify进行了一些改进,例如,它可以通知特定目录内的文件更改。
雷德尔·米兰达

38
如果此答案包括相关信息而不仅仅是链接,那将是很好的。
Darkhogg 2015年

@Darkhogg,确实很好。当我回答这个问题时,我只是成为会员的几个星期,那时我还不十分了解网站规则。回想起来,我应该投票结束这个问题,而不是提供一个糟糕的,仅链接的答案。如果无法接受,我会立即将其删除。
弗雷德里克哈米迪

4
我认为仅发布指向手册页的链接不是很有帮助;如果您给出命令安装软件包和常用示例,然后如果访问者仍然需要更多信息,链接到完整文档会更好。
伊恩·邓恩

39

您可以尝试entr在文件更改时运行任意命令的工具。文件示例:

$ ls -d * | entr sh -c 'make && make test'

要么:

$ ls *.css *.html | entr reload-browser Firefox

Changed!file.txt保存文件时打印:

$ echo file.txt | entr echo Changed!

对于目录,请使用-d,但您必须在循环中使用它,例如:

while true; do find path/ | entr -d echo Changed; done

要么:

while true; do ls path/* | entr -pd echo Changed; done

2
entr是最简单,最易组合且适用于UNIX的工具。爱它。incron可以替换为,甚至可以使用entr 流程管理器。runits6systemd
clacke

2
这个。它比inotifywait 10倍以上简单
八月

我有一个脚本定期附加到日志文件。当我entr用来监视该日志文件和touch日志时,一切正常,但是当脚本追加到该文件时,将entr失败。这可能是因为我在fstab中为我的ssd设置了noatime-但这仅停止更新访问时间而不是修改时间,因此这使我感到困惑。然后,我尝试entr -cdr使用日志更新的文件目录。识别与目录内容更改,但是-r不起作用。该entr过程刚刚结束。
对角线

1
我把这个变成了一个问题
对角线

这次真是万分感谢。优秀的。比起inotify或更改时要好得多。
尾巴

32

我使用此脚本在目录树中的更改上运行构建脚本:

#!/bin/bash -eu
DIRECTORY_TO_OBSERVE="js"      # might want to change this
function block_for_change {
  inotifywait --recursive \
    --event modify,move,create,delete \
    $DIRECTORY_TO_OBSERVE
}
BUILD_SCRIPT=build.sh          # might want to change this too
function build {
  bash $BUILD_SCRIPT
}
build
while block_for_change; do
  build
done

用途inotify-tools。查看inotifywait 手册页,了解如何自定义触发构建的内容。


这不是比赛条件吗?如果在构建过程中更改文件,它将无法捕获该文件。
FSMaxB '16

@FSMaxB是的,确实如此。如果要在构建时保存任何编辑,则在完成触发新构建后,我将再次保存其中一个源文件。
Dominykas Mostauskis '16

2
很好的例子。我修正了您的非时髦评论。
布鲁诺·布鲁诺斯基

我看到这个(我的)答案仍在接受。我想说的是,如果我现在必须这样做,则可以使用entr。查看其他答案。
Dominykas Mostauskis

5

这个脚本怎么样?使用“ stat”命令获取文件的访问时间,并在访问时间发生更改时(无论何时访问文件)运行命令。

#!/bin/bash

while true

do

   ATIME=`stat -c %Z /path/to/the/file.txt`

   if [[ "$ATIME" != "$LTIME" ]]

   then

       echo "RUN COMMNAD"
       LTIME=$ATIME
   fi
   sleep 5
done

6
最好使用inotifywait(inotify-tools),因为它在文件更新时几乎立即唤醒。(您的脚本可能需要等待最多5秒钟才能被注意到。)您的脚本还必须每5秒钟唤醒一次,生成一个进程,检查结果,然后再进入睡眠状态,而这足以“在5秒钟内将其一起入侵分钟”脚本,浪费CPU资源,应在生产代码中避免使用。
ntninja 2013年

谢谢你,我通常会用,enter但是有时候我需要在无法安装任何工具的系统上做一些事情,因此在任何系统上都可以使用的方法(好吧,我没有遇到任何问题stat都是非常有用的。我m使用这种不带循环的方法比较两次(文件夹时间戳和文件)以查看是否需要重新生成文件,然后从cron运行检查
lbutlr


2

如前所述,inotify-tools可能是最好的主意。但是,如果您出于娱乐目的而编程,则可以通过明智地应用tail -f尝试赚取黑客XP 。


有趣,但是您如何做到这一点?tail -f是阻塞指令,我们需要返回它才能启动脚本吗?
pdem '16

@pdem读取它的输出-任何输出都意味着该文件已更改。我不知道它做什么,如果该文件收缩虽然
Xen2050


1

仅出于调试目的,当我编写Shell脚本并希望其在保存时运行时,我使用以下命令:

#!/bin/bash
file="$1" # Name of file
command="${*:2}" # Command to run on change (takes rest of line)
t1="$(ls --full-time $file | awk '{ print $7 }')" # Get latest save time
while true
do
  t2="$(ls --full-time $file | awk '{ print $7 }')" # Compare to new save time
  if [ "$t1" != "$t2" ];then t1="$t2"; $command; fi # If different, run command
  sleep 0.5
done

运行为

run_on_save.sh myfile.sh ./myfile.sh arg1 arg2 arg3

编辑:在Mac OS的Ubuntu 12.04上进行了上述测试,将ls行更改为:

"$(ls -lT $file | awk '{ print $8 }')"

使用watch命令可以对此进行改进,但是我喜欢这种实现。感谢分享。
qodeninja

1

将以下内容添加到〜/ .bashrc中:

function react() {
    if [ -z "$1" -o -z "$2" ]; then
        echo "Usage: react <[./]file-to-watch> <[./]action> <to> <take>"
    elif ! [ -r "$1" ]; then
        echo "Can't react to $1, permission denied"
    else
        TARGET="$1"; shift
        ACTION="$@"
        while sleep 1; do
            ATIME=$(stat -c %Z "$TARGET")
            if [[ "$ATIME" != "${LTIME:-}" ]]; then
                LTIME=$ATIME
                $ACTION
            fi
        done
    fi
}

1

inotifywait 可以满足你。

这是一个常见的示例:

inotifywait -m /path -e create -e moved_to -e close_write | # -m is --monitor, -e is --event
    while read path action file; do
        if [[ "$file" =~ .*rst$ ]]; then             # if suffix is '.rst'
            echo ${path}${file} ': '${action}        # execute your command
            echo 'make html'
            make html
        fi
    done

但是,如何终止它?
卡里布·禅

@KalibZen您不会;在这种模式下,它将永远运行。当然,您可以使用ctrl-C或类似命令终止该作业。
三胞胎

@tripleee没关系,我使用文件标志将其停止,或使用写入功能生成包含停止标志的文件。flag=$(echo file_content)if [ flag == 'stop' ] then exit 1
Kalib Zen

0

假设您希望rake test每次在app /test /目录中修改任何ruby文件(“ * .rb”)时都运行。

只需获取监视文件的最新修改时间,然后每秒检查一次是否已更改。

脚本代码

t_ref=0; while true; do t_curr=$(find app/ test/ -type f -name "*.rb" -printf "%T+\n" | sort -r | head -n1); if [ $t_ref != $t_curr ]; then t_ref=$t_curr; rake test; fi; sleep 1; done

好处

  • 文件更改时,您可以运行任何命令或脚本。
  • 它可以在任何文件系统和虚拟机(使用Vagrant的VirtualBox上的共享文件夹)之间工作;因此,您可以在Macbook上使用文本编辑器,然后在Ubuntu(虚拟盒子)上运行测试。

警告

  • -printf选项在Ubuntu上很好用,但在MacOS上不起作用。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.