如何使用 bash 监视目录中新创建的文件?

Posted

技术标签:

【中文标题】如何使用 bash 监视目录中新创建的文件?【英文标题】:How to monitor newly created file in a directory with bash? 【发布时间】:2016-04-25 18:45:39 【问题描述】:

我有一个由一堆日志文件组成的日志目录,一旦发生系统事件,就会创建一个日志文件。我想写一个单行 bash 脚本,它始终监视文件列表并在终端上显示新创建的文件的内容。这是它的样子:

目前,我所要做的就是显示整个目录的内容:

for f in *; do cat $f; done

它缺少我想要的监控功能。我的系统的一个限制是我没有 watch 命令。我也没有任何包管理器来安装花哨的工具。我只有原始的 BSD。我确实有tail,我在想tail -F $(ls) 之类的东西,但这会拖尾每个文件而不是文件列表。

总之,我想修改我的脚本,以便我可以监控所有新创建的文件的内容。

【问题讨论】:

我不明白你的局限!为什么你没有watch?你的系统是什么版本? 【参考方案1】:

    第一种方法 - 在您的目录中使用隐藏文件(在我的示例中,它的名称为 .watch)。那么你的单线可能看起来像:

    for f in $(find . -type f -newer .watch); do cat $f; done; touch .watch
    

    第二种方法-使用inotify-tools:https://unix.stackexchange.com/questions/273556/when-a-particular-file-arrives-then-execute-a-procedure-using-shell-script/273563#273563

【讨论】:

我喜欢这个(第一种方法)。 @Simon 你最好:touch .newmark;find -newer .watch ... ; mv .newmark .watch 确保不会丢失在findtouch 之间到达的文件!! 你不需要将touch .watch 放在for 循环中以启用连续滚动吗?我认为您的脚本只会运行一次,并在目录中留下一个带有最新修改日期的 .watch 文件。【参考方案2】:

监控模式下使用inotifywait

首先是这个小演示:

    打开一个终端并运行:

    ext=(php css other)
    while :;do
        subname=''
        ((RANDOM%10))||printf -v subname -- "-%04x" $RANDOM
        date >/tmp/test$subname.$ext[RANDOM%3]
        sleep 1
      done
    

    这将创建名为 /tmp/test.php/tmp/test.css/tmp/test.other 的随机文件,但随机(大约 1 次 / 10)名称将是 /tmp/test-XXXX.[css|php|other],其中 XXXX 是十六进制随机数。

    打开另一个终端并运行:

    waitPaths=(/home,tmp)
    while read file ;do
        if [ "$file" ] &&
         ( [ -z "$file##*.php" ] || [ -z  "$file##*.css" ] ) ;then
            (($(stat -c %Y-%X $file)))||echo -n new
            echo file: $file, content:
            cat $file
        fi
      done < <(
        inotifywait -qme close_write --format %w%f $waitPaths[*]
    )
    

    这可能会产生类似的结果:

    file: /tmp/test.css, content:
    Tue Apr 26 18:53:19 CEST 2016
    file: /tmp/test.php, content:
    Tue Apr 26 18:53:21 CEST 2016
    file: /tmp/test.php, content:
    Tue Apr 26 18:53:23 CEST 2016
    file: /tmp/test.css, content:
    Tue Apr 26 18:53:25 CEST 2016
    file: /tmp/test.php, content:
    Tue Apr 26 18:53:27 CEST 2016
    newfile: /tmp/test-420b.php, content:
    Tue Apr 26 18:53:28 CEST 2016
    file: /tmp/test.php, content:
    Tue Apr 26 18:53:29 CEST 2016
    file: /tmp/test.php, content:
    Tue Apr 26 18:53:30 CEST 2016
    file: /tmp/test.php, content:
    Tue Apr 26 18:53:31 CEST 2016
    

一些解释:

waitPaths=(/home,tmp) 可以写成waitPaths=(/home /tmp) 或仅用于一个目录:waitPaths=/var/log if 条件搜索匹配*.php*.css 的文件名 (($(stat -c %Y-%X $file)))||echo -n new 将比较创建修改时间。 inotifywait -q 留下quiet(不要打印超过要求的) -m 用于 monitor 模式:命令不会终止,而是打印每个匹配的事件。 -e close_write 只对指定类型的事件作出反应。 -f %w%f 输出格式:path/file

另一种方式:

还有一个更复杂的示例:

监听两种事件 (CLOSE_WRITE | CREATE) 在发生 CLOSE_WRITE 事件时,使用新文件标志列表了解哪些文件是新文件。

在第二个控制台中,点击 Ctrl+C,或者在新终端中,点击:

waitPaths=(/home,tmp)
declare -A newFiles
while read path event file; do
    if [ "$file" ] && ( [ -z "$file##*.php" ] || [ -z "$file##*.css" ] ); then
        if [ "$event" ] && [ -z "$event//*CREATE*" ]; then
            newFiles[$file]=1
        else
            if [ "$newFiles[$file]" ]; then
                unset newFiles[$file]
                echo NewFile: $file, content:
                sed 's/^/>+ /' $file
            else
                echo file: $file, content:
                sed 's/^/>  /' $path/$file
            fi
        fi
    fi
done < <(inotifywait -qme close_write -e create $waitPaths[*])

可能会产生类似的东西:

file: test.css, content:
>  Tue Apr 26 22:16:02 CEST 2016
file: test.php, content:
>  Tue Apr 26 22:16:03 CEST 2016
NewFile: test-349b.css, content:
>+ Tue Apr 26 22:16:05 CEST 2016
file: test.css, content:
>  Tue Apr 26 22:16:08 CEST 2016
file: test.css, content:
>  Tue Apr 26 22:16:10 CEST 2016
file: test.css, content:
>  Tue Apr 26 22:16:13 CEST 2016

【讨论】:

【参考方案3】:

使用bash 监视旧文件中的新文件和新行

还有另一种解决方案,使用一些 bashisms,例如 关联数组

示例:

wpath=/var/log
while : ;do
    while read -a crtfile ;do
        if [ "$crtfile:0:1" = "-" ] &&
          [ "$crtfile[8]##*." != "gz" ] &&
          [ "$files[$crtfile[8]]:-0" -lt $crtfile[4] ] ;then
            printf "\e[47m## %-14s :- %(%a %d %b %y %T)T ##\e[0m\n" $crtfile[8] -1
            tail -c +$[1+$files[$crtfile[8]]:-0] $wpath/$crtfile[8]
            files[$crtfile[8]]=$crtfile[4]
        fi
    done < <( /bin/ls -l $wpath )
    sleep 1
done

这将转储/var/log中的每个文件(文件名不以.gz结尾),并注意修改或新文件,然后转储新行。

演示:

    在第一个终端控制台中,点击:

    ext=(php css other)
    ( while :; do
        subname=''
        ((RANDOM%10)) || printf -v subname -- "-%04x" $RANDOM
        name=test$subname.$ext[RANDOM%3]
        printf "%-16s" $name
         
            date +"%a %d %b %y %T" | tee /dev/fd/5
            fortune /usr/share/games/fortunes/bofh-excuses
         >> /tmp/$name
        sleep 1
    done ) 5>&1
    

    您需要安装 fortune 并带有 BOFH 借口 图书馆。

    如果你真的没有fortune,你可以改用这个:

    LANG=C ext=(php css other)
    ( while :; do
        subname=''
        ((RANDOM%10)) || printf -v subname -- "-%04x" $RANDOM
        name=test$subname.$ext[RANDOM%3]
        printf "%-16s" $name
         
            date +"%a %d %b %y %T" | tee /dev/fd/5
            for ((1; RANDOM%5; 1))
            do
                printf -v str %$[RANDOM&12]s
                str=$str// /blah, 
                echo $str%, .
            done
         >> /tmp/$name
        sleep 1
    done ) 5>&1
    

    这可能会输出如下内容:

    test.css        Thu 28 Apr 16 12:00:02
    test.php        Thu 28 Apr 16 12:00:03
    test.other      Thu 28 Apr 16 12:00:04
    test.css        Thu 28 Apr 16 12:00:05
    test.css        Thu 28 Apr 16 12:00:06
    test.other      Thu 28 Apr 16 12:00:07
    test.php        Thu 28 Apr 16 12:00:08
    test.css        Thu 28 Apr 16 12:00:09
    test.other      Thu 28 Apr 16 12:00:10
    test.other      Thu 28 Apr 16 12:00:11
    test.php        Thu 28 Apr 16 12:00:12
    test.other      Thu 28 Apr 16 12:00:13
    

    在第二个终端控制台中,点击:

    declare -A files
    wpath=/tmp
    while :; do
        while read -a crtfile; do
            if [ "$crtfile:0:1" = "-" ] && [ "$crtfile[8]:0:4" = "test" ] &&
             ( [ "$crtfile[8]##*." = "css" ] || [ "$crtfile[8]##*." = "php" ] ) &&
             [ "$files[$crtfile[8]]:-0" -lt $crtfile[4] ]; then
                printf "\e[47m## %-14s :- %(%a %d %b %y %T)T ##\e[0m\n" $crtfile[8] -1
                tail -c +$[1+$files[$crtfile[8]]:-0] $wpath/$crtfile[8]
                files[$crtfile[8]]=$crtfile[4]
            fi
        done < <(/bin/ls -l $wpath)
        sleep 1
    done
    

每秒都会这样

    监视目录中的所有条目

      搜索文件(第一个字符是-), 搜索以test 开头的文件名, 搜索以cssphp 结尾的文件名, 将已打印的尺寸与新的文件尺寸进行比较, 如果新尺寸更大, 使用tail -c 和 打印出新字节 存储新的已打印的尺寸 睡 1 秒

    这可能会输出如下内容:

    ## test.css       :- Thu 28 Apr 16 12:00:09 ##
    Thu 28 Apr 16 12:00:02
    BOFH excuse #216:
    
    What office are you in? Oh, that one.  Did you know that your building was built over the universities first nuclear research site? And wow, aren't you the lucky one, your office is right over where the core is buried!
    Thu 28 Apr 16 12:00:05
    BOFH excuse #145:
    
    Flat tire on station wagon with tapes.  ("Never underestimate the bandwidth of a station wagon full of tapes hurling down the highway" Andrew S. Tannenbaum) 
    Thu 28 Apr 16 12:00:06
    BOFH excuse #301:
    
    appears to be a Slow/Narrow SCSI-0 Interface problem
    ## test.php       :- Thu 28 Apr 16 12:00:09 ##
    Thu 28 Apr 16 12:00:03
    BOFH excuse #36:
    
    dynamic software linking table corrupted
    Thu 28 Apr 16 12:00:08
    BOFH excuse #367:
    
    Webmasters kidnapped by evil cult.
    ## test.css       :- Thu 28 Apr 16 12:00:10 ##
    Thu 28 Apr 16 12:00:09
    BOFH excuse #25:
    
    Decreasing electron flux
    ## test.php       :- Thu 28 Apr 16 12:00:13 ##
    Thu 28 Apr 16 12:00:12
    BOFH excuse #3:
    
    electromagnetic radiation from satellite debris
    

    注意:如果某些文件在两次检查之间被修改了多次,所有修改将在下次检查时打印。

【讨论】:

@return0:这个解决方案很好,因为它只使用bash、lstail(和sleep,但您可以改用read -t)。这会打印所有(甚至是新的)文件中的所有文件,但只打印 新行!你试过这个解决方案了吗?【参考方案4】:

如果你愿意,你可以把它塞进一个单行里,但我建议只在后台运行脚本:

#!/bin/bash

[ ! -d "$1" ] && 
    printf "error: argument is not a valid directory to monitory.\n"
    exit 1


while :; fname="$1/$(inotifywait -q -e modify -e create --format '%f' "$1")"; do
    cat "$fname"
done

它将监视作为第一个参数给出的目录,以及cat 该目录中任何新的或更改的文件。示例:

$ bash watchdir.sh my_logdir &

然后catmy_logdir 中的新文件或更改的文件。

【讨论】:

Monitor mode loopforever loopone shot inotifywait 更好,因为即使在 @ 987654327@ 未运行。见my answer 是的,我玩过这个,我只是不想让read 阻止。我想这并不重要。【参考方案5】:

虽然不是很好,但以下给出(并重复)当前目录中最新文件的最后 50 行:

while true; do tail -n 50 $(ls -Art | tail -n 1); sleep 5; done

【讨论】:

【参考方案6】:

您可以使用 cronjob 每分钟刷新一次:

$crontabe -e


 * * * * * /home/script.sh

如果您需要在一分钟内刷新,您可以在脚本中使用命令“sleep”。

【讨论】:

以上是关于如何使用 bash 监视目录中新创建的文件?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 inotifywait 监视文件夹而不是文件夹中的文件

如何将上传的文件从临时目录移动到永久目录中新创建的文件夹

使用 bash 监视目录中的现有文件和新文件

在监视文件夹中新文件的小脚本中,该脚本似乎正在查找错误的文件

监视添加到目录的文件大小(Bash)

用于监视新文件目录的 bash 脚本