使用tail -f并在读取时监视文件并执行命令?

时间:2014-09-07 16:57:37

标签: linux bash unix watch tail

每次更改文件时,我都会尝试查看文件并执行一次命令,理想情况下只需使用本机bash命令。

这是我所拥有的,但如何检查我是否已到达文件的开头或结尾?我意识到tail -f没有阅读EOF,所以我怎么知道我已经到达了文件的末尾?

 tail -f source_file.js | while read line || [[ -n "$line" ]]; 
     # how do I execute a command here just **once**?
 done

只要它们是本机bash命令和大约一行,就会接受不使用tailwhile read的答案。

也许每次调用时我都可以将变量归零?

6 个答案:

答案 0 :(得分:5)

它绝不是本机bash解决方案,但你可以使用libinotify来做你想做的事情:

while inotifywait -qqe modify file; do 
    echo "file modified"
done

这会监视对file的修改,并在它们发生时在循环内执行操作。 -qq开关禁止程序的输出,默认情况下,每当文件发生某些事件时都会打印一条消息。

答案 1 :(得分:4)

来自男人:

  

<强> -f

     

-f选项导致tail在到达文件结尾时不会停止,而是等待将其他数据附加到输入。

因此,如果您想要监视和执行操作,需要将脚本分解为两个进程

  • 将显示内容tail(如果您希望亲自监控更改)
  • 秒将监控变更并执行操作

  • 您应该使用例如perlpython什么可以监视文件结束并在到达时执行某些操作(例如,运行bash脚本)。

bash灵魂可以基于文件修改时间

file="./file"

runcmd() {
        echo "======== file $1 is changed ============"
}

#tail -f "$file" &   #uncomment 3 lines is you want pesonally watch the file
#tailpid=$!
#trap "kill $tailpid;exit" 0 2    #kill the tail, when CTRL-C this script

lastmtime=0
while read -r mtime < <(stat -c '%Z' "$file")
do
        if [[ $lastmtime != $mtime ]]
        then
                lastmtime=$mtime
                runcmd "$file"
        fi
        sleep 1
done

添加了另一种基于标准perl的解决方案

perltail() {
#adapted from the perlfaq5
#http://perldoc.perl.org/perlfaq5.html#How-do-I-do-a-tail--f-in-perl%3f
perl -MTime::HiRes=usleep -Mstrict -Mautodie -e '
$|=1;
open my $fh, "<", "$ARGV[0]";
my $curpos;
my $eof=1;
for (;;) {
    for( $curpos = tell($fh); <$fh>; $curpos =tell($fh) ) {
        print;
        $eof=1
    }
    print "=EOF-reached=\n" if $eof;
    $eof=0;
    usleep(1000); #adjust the microseconds
    seek($fh, $curpos, 0);
}' "$1"
}

eof_action() {
    echo "EOF ACTION"
    printf "%s\n" "${newlines[@]}"  #new lines received from the last eof
    newlines=()         #empty the newlines array
}

perltail "./file" | while read -r line
do
    if [[ $line =~ =EOF-reached= ]]
    then
        eof_action
        continue
    fi
    #do something with the received lines - if need
    #for example, store new lines into variable for processing in the do_action and like
    newlines+=($line)
done

普林西:

  • perltail bash函数运行tail -f的perl实现,此外,当到达文件结尾时,它会将 MARK 输出到输出,这里:=EOF-reached=
  • bash while read正在寻找 MARK 并仅在该标记存在的情况下执行操作 - 例如只有在文件结束时才会到达。

答案 2 :(得分:1)

我不确定你做某事的确切含义“每次更改文件时”或“约一行”的实现。 tail命令通常以面向行的方式使用,所以我想在单个write(2)中附加到文件的500行文本是一个值得只调用一次命令的更新

但是,在几十毫秒的延迟之后会说几十行?您希望多久调用一次命令?

如果libinotify可用,请根据Fenech先生使用它。如果您尝试使用更基本的shell实用程序,find(1)也可用于注意一个文件何时变得比另一个文件更新。

例如,一个脚本,它监视文件,并在更新时运行提供的命令,每隔1秒轮询一次。

#!/bin/sh

[ $# -ge 2 ] || { echo "Usage: $(basename $0) <file> <cmd...>" 1>&2; exit 1; }
[ -r "$1"  ] || { echo "$1: cannot read" 1>&2; exit 1; }

FILE=$1; shift

FTMP=$(mktemp /tmp/$(basename "$FILE")_tsref.XXXXXX)
trap 'rm -f "$FTMP"' EXIT

touch -r "$FILE" "$FTMP"

while true; do
    FOUT=$(find "$FILE" -newer "$FTMP") || exit $?
    if [ "$FOUT" = "$FILE" ]; then
        touch -r "$FILE" "$FTMP"
        eval "$@"
    else
        sleep 1
    fi
done

答案 3 :(得分:1)

很多人已经写过这个问题,可能需要澄清一些原则。

如果你想在监视器处理读取行(无论是什么,例如tail或其他任何东西)时等待EOF,你必须指定等待间隔,换句话说,被认为是&#34 ;新线&#34;在文件中。

想象:

  • 你将达到EOF,但是在100微秒的新线到达​​时,该过程将读取它并到达另一个EOF。属于上一行线的新线? (可能是的)。
  • 如果新线路在5秒内到达怎么办?这可能是一个新的线条。

因此,正如您所看到的,指定您为&#34;新块&#34;所考虑的时间。线条是绝对必要的。换句话说,如果您在指定的时间间隔内多次达到EOF - 则仅表示一个EOF。 (比如在100微秒内达到EOF两次)。

因此tail -f本身使用 1秒超时作为默认值,而在GNU版本中,您可以使用-s参数更改此值。来自文档:

  

-s, - sleep-interval = N

     

使用-f,在迭代之间休眠约N秒(默认值为1.0);同   inotify和--pid = P,每N秒至少检查一次进程P

此外,您可以在尾部的source code中查看此内容。

对于inotifyinotofy-tools,原则是相同的。 (并且尾部,(取决于您的发行版)可以使用inotofylib本身))。使用inotifylib必须调用函数

struct inotify_event* inotifytools_next_event (int timeout)

您的程序应经常调用此函数或inotifytools_next_events();在调用此函数之间,inotify事件将在内核中排队,最终队列将溢出,您将错过一些事件。 (see here)。

同样,时间间隔是必不可少的(所有常用工具都默认为1秒)。

关于使用-n $line尝试的解决方案。它无法工作,因为当到达EOF时,尾部永远不会返回空行。尾部只返回得到的最后一行,并等待新行(并在指定的时间间隔内检查它们)。

要点:

  • 你必须在你想要的东西上指定超时检查EOF条件(如果不是,可能1秒就可以了 - 改变上述脚本的睡眠时间。
  • 以上所有解决方案都正常运行,没关系
  • 最后:
  

只要能够接受不使用尾巴或阅读时的答案   它们是本机bash命令,大约有一行。

由于上述原因,

没有任何意义。

希望这有帮助。

答案 4 :(得分:1)

如果使用的编辑器没有使用原子保存,则可以使用

tail -f source_file.js  2>&1 > /dev/null | while read line; do echo 'do any command here'; done 

Atomic save can be turned off easily in sublime

答案 5 :(得分:1)

这将允许你使用tail:

while read line; do
#code goes here
done < <(tail -f $FILE)

但是,循环每行运行一次(每次更改文件时不会运行一次)。使用< <(stat -c '%Z' "$file")与睡眠的好处是它可以捕获每一个变化。要使用stat以任何合理的确定性来执行此操作,您必须删除sleep命令,但这会占用CPU资源。

你可以在循环中使用带有附加逻辑的全局变量来阻止主命令在&#34; x行和/或y秒内再次运行&#34;防止为每条线路运行命令,但这仍然不可靠并且很快变得麻烦。

你最好的选择是使用inotifywait(来自inotify-tools),因为它为文件设置了一个监听器,而不是像你一样每秒(或连续)进行轮询与stat

[编辑]

或者,如果某个关键字或序列在每次更新时都添加到文件中一次,只需使用if $(grep --quiet "keyword" <<< $line)进行检查并输入您的代码在if语句中。