【问题标题】:How to run a shell script when a file or directory changes?文件或目录更改时如何运行 shell 脚本?
【发布时间】:2011-05-02 21:24:49
【问题描述】:

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

我怎样才能轻松做到这一点?

【问题讨论】:

  • 我有一个帖子我觉得基本一样:*.com/q/2972765/119790
  • @MerlynMorgan-Graham 我会将其移至超级用户,因为答案可能与编程无关 - 即可能有一些程序或配置选项可以使用,而无需任何编程。我有同样的问题,并首先搜索超级用户:p

标签: linux shell inotify


【解决方案1】:

想要跟踪单个文件的鱼壳用户的快速解决方案:

while true
    set old_hash $hash
    set hash (md5sum file_to_watch)
    if [ $hash != $old_hash ]
        command_to_execute
    end
    sleep 1
end

如果在 macos 上,请将 md5sum 替换为 md5

【讨论】:

    【解决方案2】:

    使用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
    

    【讨论】:

    • incron 是另一种选择。
    • fanotify 是另一种选择。建立在 inotify 之上。它对 inotify 进行了一些改进,例如它可以通知特定目录中的文件更改。
    • 如果这个答案包含相关信息而不仅仅是一个链接,那就太好了。
    • @Darkhogg,确实不错。当我回答这个问题时,我才加入了几个星期,当时我对网站规则还不是很了解。回想起来,我应该投票结束这个问题,而不是提供一个糟糕的、仅链接的答案。如果它不被接受,我会立即删除它。
    • 我不认为简单地发布一个手册页的链接是很有帮助的。如果您给出安装软件包的命令,以及常见用法示例,如果访问者仍需要更多信息,然后链接到完整文档会更好。
    【解决方案3】:

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

    只需获取所监视文件的最近修改时间,并检查该时间是否已更改。

    脚本代码

    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
    

    好处

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

    警告

    • -printf 选项适用于 Ubuntu,但不适用于 MacOS。

    【讨论】:

      【解决方案4】:

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

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

      或:

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

      或者当文件file.txt被保存时打印Changed!

      $ 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
      

      【讨论】:

      • entr 是最简单、最可组合的 unix-y 工具。爱它。 incron 可以替换为 entr 和进程管理器,如 runits6 甚至 systemd
      • 这个。它比 inotifywait 简单 10 倍
      • 我有一个脚本定期附加到日志文件中。当我使用entr 监视该日志文件和touch 日志时,一切正常,但是当脚本附加到文件时,entr 失败。这可能是因为我在我的 fstab 中为我的 ssd 设置了 noatime - 但这只会停止更新访问时间而不是修改时间,所以这让我感到困惑。然后我在使用日志更新的文件目录上尝试了entr -cdr。这可以识别目录内容的更改,但 -r 不起作用。 entr 进程刚刚结束。
      • 我把它变成了a question
      • 谢谢你。优秀。比 inotify 或 when-changed 要好得多。
      【解决方案5】:

      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
      【解决方案6】:

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

      #!/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。检查inotifywaitman page,了解如何自定义触发构建的内容。

      【讨论】:

      • 这不是有竞争条件吗?如果您在构建过程中更改文件,它不会捕获它。
      • @FSMaxB 是的,确实如此。如果我在构建时保存任何编辑,我会在完成后再次保存其中一个源文件以触发新构建。
      • 很好的例子。我修复了你的非 bash cmets。
      • 我看到这个(我的)答案仍然被赞成。我想说,如果我现在必须这样做,我会使用entr。查看其他答案。
      【解决方案7】:

      将以下内容添加到 ~/.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
      }
      

      【讨论】:

      • 在 MacOS 上使用 ATIME=$(stat -f %m "$TARGET"),在 Ubuntu 上使用 ATIME=$(stat -c %Y "$TARGET")
      【解决方案8】:

      仅出于调试目的,当我编写一个 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
      

      编辑:以上在 Ubuntu 12.04 上测试,对于 Mac OS,将 ls 行更改为:

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

      【讨论】:

      • 这可能会通过watch 命令得到改善,但我喜欢这个实现。感谢分享。
      【解决方案9】:

      这个脚本怎么样?使用“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
      

      【讨论】:

      • 最好使用inotifywait (inotify-tools),因为它几乎会在文件更新时立即唤醒。 (您的脚本最多会等待 5 秒才能注意到。)您的脚本还必须每 5 秒唤醒一次,生成一个进程,检查结果然后重新进入睡眠状态,而这对于“在 5 分钟内将其破解”已经足够了分钟”-脚本,它会浪费CPU资源,应该在生产代码中避免。
      • 谢谢你,我通常使用enter,但有时我需要在无法安装任何工具的系统上做一些事情,所以一种适用于任何系统的方法(好吧,我没有'在没有stat 的情况下遇到任何事情都非常有帮助。我正在使用这种没有循环的方法来比较两次(文件夹时间戳和文件)以查看是否需要重新生成文件,然后从 cron 运行检查。
      • @VDR 脚本何时可以工作......我是否必须手动运行示例脚本来检查文件中的任何更改,例如example.txt 出现。据我了解,如果发生任何更改,它将运行命令/运行另一个脚本...请清除
      • 我同意@Ibutlr 有时我们有一些机器我们无法在那里安装任何工具
      • @TAMIMHAIDER 一旦你调用脚本,它就会在循环中运行,比较给定文件的访问时间,如果被访问,它会运行echo RUN COMMAND。您可以将“echo”命令替换为文件更改时要运行的其他脚本/命令。
      【解决方案10】:

      这是另一个选项:http://fileschanged.sourceforge.net/

      请特别参阅“示例 4”,它“监控目录并归档任何新的或更改的文件”。

      【讨论】:

        【解决方案11】:

        如前所述,inotify-tools 可能是最好的主意。但是,如果您是为了好玩而编程,您可以尝试通过tail -f 的明智应用来赚取黑客 XP。

        【讨论】:

        • 很有趣,但是你是怎么做到的呢? tail -f 是一个阻塞指令,我们需要它返回才能启动脚本?
        • @pdem 读取它的输出 - 任何输出都意味着文件已更改。我不确定如果文件 收缩 会发生什么
        【解决方案12】:

        检查内核文件系统监视器守护进程

        http://freshmeat.net/projects/kfsmd/

        这是一个操作方法:

        http://www.linux.com/archive/feature/124903

        【讨论】:

        • 第二个链接坏了
        最近更新 更多