【问题标题】:Bash script, watch folder, execute commandBash 脚本,监视文件夹,执行命令
【发布时间】:2011-09-22 10:43:13
【问题描述】:

我正在尝试创建一个带有 2 个参数的 bash 脚本:

  • 目录
  • 命令

我想观察目录参数的变化:当某些东西发生变化时,脚本应该执行命令。

我运行的是 MacOS,而不是 Linux;任何指针或外部资源都会有很大帮助,因为我已经看到这很难实现。真的,OI 正在尝试模仿 SASS 的手表功能。

#!/bin/bash

#./watch.sh $PATH $COMMAND

DIR=$1  

ls -l $DIR > $DIR/.begin
#this does not work
DIFFERENCE=$(diff .begin .end)

if [ $DIFFERENCE = '\n']; then
    #files are same
else
    $2
fi 

ls -l $DIR > $DIR/.end

【问题讨论】:

    标签: macos bash shell


    【解决方案1】:

    持续递归监控文件夹(md5)并在更改时执行命令:

    daemon() {
        chsum1=""
    
        while [[ true ]]
        do
            chsum2=`find src/ -type f -exec md5 {} \;`
            if [[ $chsum1 != $chsum2 ]] ; then           
                if [ -n "$chsum1" ]; then
                    compile
                fi
                chsum1=$chsum2
            fi
            sleep 2
        done
    }
    

    在我的 OS X 上工作,因为我没有 digest

    在 Linux 上,您可以使用 md5sum 代替 md5 命令。

    【讨论】:

    • 很好,不过我建议用chsum1=$chsum2 替换chsum1=...。否则,compile 期间发生的变化将不会被注意到。
    • OS X 有“fswatch”,这里:github.com/alandipert/fswatchIt 这是一个使用 FSEvents API 的小命令,所以它做同样的事情,但会节省你的 CPU。对于大型项目来说更好(不需要对所有内容进行 MD5)。
    • +1。不多说,find src/ -type f -mtime -5s 速度明显更快,并且不需要 cpu,因为它不会在每个文件上执行。它会检查过去 5 秒内是否有任何变化。
    • @Devrim 不错!这找不到删除但适用于我的目的。
    • 而不是每次都对文件进行散列,只需区分时间戳ls --full-time $file | awk '{ print $7 }';我更喜欢这个实现 > stackoverflow.com/a/25869844/177389 更好。
    【解决方案2】:

    我不敢相信还没有人发布这个。

    首先确保已安装inotify-tools

    然后像这样使用它们:

    logOfChanges="/tmp/changes.log.csv" # Set your file name here.
    
    # Lock and load
    inotifywait -mrcq $DIR > "$logOfChanges" &
    IN_PID=$$
    
    # Do your stuff here
    ...
    
    # Kill and analyze
    kill $IN_PID
    cat "$logOfChanges" | while read entry; do
       # Split your CSV, but beware that file names may contain spaces too.
       # Just look up how to parse CSV with bash. :)
       path=... 
       event=...
       ...  # Other stuff like time stamps?
       # Depending on the event…
       case "$event" in
         SOME_EVENT) myHandlingCode path ;;
         ...
         *) myDefaultHandlingCode path ;;
    done
    

    或者,在inotifywait 上使用--format 而不是-c 也是一个想法。

    只需man inotifywaitman inotifywatch 了解更多信息。

    【讨论】:

    • 刚刚尝试在 Mac 上安装 inotify-tools 失败,如果您有兴趣,请发布 github issue
    • 我为您的 github 问题发布了解决方案。这只是一个 autoconf 版本不匹配。 :)
    • 我注意到inotify-tools 需要一个支持inotify 的Linux 内核。除非您对 Mac OS X 和 inotify 有一些其他人不知道的知识,否则没有直接的 inotify 支持(但 FSEvents API — 文件系统事件 — 将接近所需的功能,即使界面不同)。
    • @JonathanLeffler:fswatch,它使用FSEvents,似乎是一个不错的全功能选择:stackoverflow.com/questions/1515730/…
    【解决方案3】:

    这是一个观察文件夹变化并在更新时运行 less 编译器的示例。作为先决条件,您需要npm 和这些模块onchange。节点社区有一大堆不同的监视命令(如 onchange),我不知道有任何编译自包含二进制文件的命令。

    npm install less onchange -g
    

    然后你可以使用类似的东西:

    onchange "./stylesheets/*.less" -- lessc main.less > main.css
    

    我更喜欢 BASH 命令而不是我之前给出的 Grunt answer

    【讨论】:

    • 因为您的问题直接暗示了 bash,我希望答案以 bash 为重点,而不是 npm。我强烈建议选择投票数较高的 bash 答案。
    【解决方案4】:

    方法 1:

    #!/bin/sh
    
    check() {
        dir="$1"
        chsum1=`digest -a md5 $dir | awk '{print $1}'`
        chsum2=$chsum1
    
        while [ $chsum1 -eq $chsum2 ]
        do
            sleep 10
            chsum2=`digest -a md5 $dir | awk '{print $1}'`
        done
    
        eval $2
    }
    
    check $*
    

    这个脚本有两个参数[目录,命令]。脚本每 10 秒执行一次 check() 以查看文件夹已更改。如果不是,它会休眠并重复循环。

    如果文件夹发生变化,它evals 你的命令。

    方法 2:
    使用 cron 监控文件夹。

    您必须安装 incron:

     sudo apt-get install incron
    

    然后您的脚本将如下所示:

    #!/bin/bash
    eval $1
    

    (您不需要指定文件夹,因为监视指定目录将是 cron 的工作)

    可以在此处找到完整的工作示例:

    http://www.errr-online.com/index.php/2011/02/25/monitor-a-directory-or-file-for-changes-on-linux-using-inotify/

    【讨论】:

    • 编辑做你需要的。享受吧。
    • 您可以使用md5sum(或者,更可靠的是sha1sum)。
    • 这是一种使用 crons 的新方法
    【解决方案5】:

    可能是最快的方法..(在 1G git repo 上,1 秒内返回。)

    #!/bin/bash
    
    watch() {
    
        echo watching folder $1/ every $2 secs.
    
    while [[ true ]]
    do
        files=`find $1 -type f -mtime -$2s`
        if [[ $files == "" ]] ; then
            echo "nothing changed"
        else
                echo changed, $files
        fi
        sleep $2
    done
    }
    
    watch folder 3
    

    【讨论】:

    • 感谢您的评论。未来的读者:当我发布这个答案时,除了 grunt 没有其他选择 - 现在我看到上面有一些答案使用相同的方法但更好;改用他们的解决方案。
    • 这非常简短,因为您不必安装额外的依赖项。它没有捕捉到的一件事是删除;仅添加/更新。
    【解决方案6】:

    在 Mac OS X 中,您只需按住 Control 键单击文件夹,然后单击“文件夹操作设置”。这将允许您将操作附加到文件夹,即要运行的脚本。

    OS X 带有许多预构建的脚本,或者您可以创建自己的。

    【讨论】:

    • 文件夹操作的触发非常缓慢。
    【解决方案7】:

    差不多 3 年后,我会推荐这个基于 grunt 的解决方案。

    我在这里创建了一个工作示例https://github.com/reggi/watch-execute

    这是Gruntfile.js

    module.exports = function (grunt) {
      grunt.initConfig({
        shell: {
          run_file:{
            command: 'sh ./bash.sh',
            options: {
                stdout: true
            }
          }
        },
        watch: {
          run_file: {
            files: ["./watchme/*"],
            tasks: ["shell:run_file"]
          }
        }
      });
      grunt.loadNpmTasks('grunt-contrib-watch');
      grunt.loadNpmTasks('grunt-shell');
    };
    

    【讨论】:

      【解决方案8】:

      我编写了一个名为watchfile 的通用实用程序来简化这些操作。

      它不如inotifywatch 强大,但我更喜欢更简单、不那么冗长的实用程序。

      对于所需的任务,您要监视当前目录中的任何文件是否已被修改。递归列出当前目录中的所有文件:

      find . -type f
      

      要输出每个文件的时间戳信息:

      find . -type f -print0 | xargs -0 stat
      

      现在,您可以使用watchfile 实用程序监控此输出,并在此信息更改时执行命令CMD

      watchfile -s "find . -type f -print0 | xargs -0 stat" -e CMD
      

      【讨论】:

        【解决方案9】:
        #!/bin/bash
        
        # Author: Devonte
        # NGINX WATCH DAEMON
        # Place file in root of nginx folder /etc/nginx
        # This will test your nginx config on any change and
        # if there are no problems it will reload your configuration
        # USAGE: sh nginx-watch.sh
        
        dir=`dirname $0`
        
        checksum_initial=`tar -cf - $dir | md5sum | awk '{print $1}'`
        checksum_now=$checksum_initial
        
        # Start nginx
        nginx
        
        while true
        do
            sleep 3
            checksum_now=`tar -cf - $dir | md5sum | awk '{print $1}'`
        
            if [ $checksum_initial != $checksum_now ]; then
                echo "[ NGINX ] A configuration file changed. Reloading..."
                nginx -t && nginx -s reload;
            fi
        
            checksum_initial=$checksum_now
        done
        

        【讨论】:

        • 我喜欢使用tarmd5sum 来确定文件夹是否已更改。为了让它在 macOS 上运行,我不得不稍微调整一下:checksum_initial=`tar -cf - $dir | md5`
        【解决方案10】:

        为什么不使用 AppleScript

        http://www.tuaw.com/2009/03/26/applescript-exploring-the-power-of-folder-actions-part-iii/

        on adding folder items to this_folder after receiving added_items
        tell application "Finder"
        ...
        

        【讨论】:

        • 好的,让我纠正一下。这适用于接收文件但不响应更改。我花了整整一周的时间试图模仿 Dropbox,发现了这一点。所以我的观点是你不能完全“观察”一个文件夹,尤其是文件夹操作。 stackoverflow.com/questions/6476166/…
        【解决方案11】:

        如果您只需要检查在顶层创建/删除的文件(不检查子文件夹),您可能需要使用以下内容。

        它使用的资源很少,因此可以快速反应,我用它来检查更改的文件。

        #!/bin/bash
        
        file="$1"
        shift
        
        tmp=$(mktemp)
        trap 'rm "$tmp"' EXIT
        
        while true; do
            while [ ! "$tmp" -ot "$file" ]; do
                sleep 0.5
            done
            eval "$@ &"
            echo $! > "$tmp"
            wait
        done
        

        【讨论】:

          【解决方案12】:

          这是一个可以使用的模板,它将每 120 秒检查一次传递的目录中的更改,并在创建目录、文件或名称管道时发出通知。如果您还想在删除某些内容时运行命令,请查看我在 stackoverflow 上的其他答案以获取更多循环示例。

          #!/usr/bin/env bash
          Var_dir="${1:-/tmp}"
          Var_diff_sleep="${2:-120}"
          Var_diff_opts="--suppress-common-lines"
          Func_parse_diff(){
              _added="$(grep -E '>' <<<"${@}")"
              if [ "${#_added}" != "0" ]; then
                  mapfile -t _added_list <<<"${_added//> /}"
                  _let _index=0
                  until [ "${#_added_list[@]}" = "${_index}" ]; do
                      _path_to_check="${Var_dir}/${_added_list[${_index}]}"
                      if [ -f "${_path_to_check}" ]; then
                          echo "# File: ${_path_to_check}"
                      elif [ -d "${_path_to_check}" ]; then
                          echo "# Directory: ${_path_to_check}"
                      if [ -p "${_path_to_check}" ]; then
                          echo "# Pipe: ${_path_to_check}"
                      fi
                      let _index++
                  done
                  unset _index
              fi
          }
          Func_watch_bulk_dir(){
              _current_listing=""
              while [ -d "${Var_dir}" ]; do
                  _new_listing="$(ls "${Var_dir}")"
                  _diff_listing="$(diff ${Var_dec_diff_opts} <(${Var_echo} "${_current_listing}") <(${Var_echo} "${_new_listing}"))"
                  if [ "${_diff_listing}" != "0" ]; then
                      Func_parse_diff "${_diff_listing}"
                  fi
                  _current_listing="${_new_listing}"
                  sleep ${Var_diff_sleep}
              done
          }
          

          提示如果您将上面的 echo 行替换为 eval &lt;some command&gt; 为您监控的每种类型的操作,您将更接近于操作自动化。如果您想查看在脚本中使用上述内容时的样子,请查看最新的script version,了解我一直致力于通过 gpg 和 tar 实现加密和解密自动化的项目。

          【讨论】:

            【解决方案13】:

            Radek's answer“有点”为我工作(OSx),但它减慢了我的终端。我对其进行了一些修改,这就是我认为对我有用的:

            #!/bin/bash
            
            daemon() {
                chsum1=""
                targetFolder=path-to-target-folder
            
                while [[ true ]]
                do
                    chsum2=`find ${targetFolder} -type f | xargs stat -f "%m" | md5`
                    if [[ $chsum1 != $chsum2 ]] ; then
                        # echo the date to indicate we are updating   
                        date
                        ./do-something.sh
                        # tracks the check-sum
                        chsum1=$chsum2
                    fi
                    # sleep for 2 secs
                    sleep 2
                done
            }
            
            daemon 
            

            肉在:

            chsum2=`find ${targetFolder} -type f | xargs stat -f "%m" | md5`
            

            表示find all files in ${targetFolder}, pipe that into stat -f "%m" 1-by-1 and then pipe that into md5stat -f "%m" [filepath] 为您提供上次修改的时间戳。

            请帮我改进它。谢谢!

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2017-01-04
              • 2018-02-15
              • 1970-01-01
              • 2016-07-25
              • 2012-10-16
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多