【问题标题】:Watch file and execute command using tail -f and while read?使用 tail -f 监视文件并执行命令,同时读取?
【发布时间】:2014-10-31 23:42:28
【问题描述】:

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

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

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

不使用tailwhile read 的答案将被接受,只要它们是本机bash 命令并且大约一行。

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

【问题讨论】:

  • 嗯?你永远不会用tail -f 得到EOF - 如果follows 永远。

标签: linux bash unix watch tail


【解决方案1】:

来自男人:

-f

-f 选项使 tail 在到达文件末尾时不停止,而是等待附加数据附加到输入。

所以,如果你想监控并执行操作,需要将脚本分成两个进程

  • 将显示内容tail(如果您想亲自监控更改)
  • second 将监控更改并执行操作

  • 您应该使用例如 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 并仅在该标记存在时运行操作 - 例如仅在到达文件结尾时。

【讨论】:

  • 嗯?这没有任何意义。
  • 是的,这就是[[-n "$line"]] 的用途。 stackoverflow.com/a/5010679/1376627
  • @tripleee - 尾部永远不会以 -f 结尾 - 所以最简单的方法是不使用 -f - 想知道什么对你没有意义......
  • @tripleee 改变了解决方案 - 现在有意义吗? ;) :)
  • 我在这里看不到tail 的用途。另外,我想lastmtime应该在脚本启动时初始化为文件的mtime。
【解决方案2】:

这绝不是原生 bash 解决方案,但您可以使用 libinotify 来做您想做的事:

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

这会监视对file 的修改,并在发生时执行循环内的操作。 -qq 开关抑制程序的输出,默认情况下,每次文件发生问题时都会打印一条消息。

【讨论】:

  • 我必须使用until 而不是while。显然inotifywait 返回退出代码 1。 ``` 直到 inotifywait -qqe modify myfile.tex;做回声“修改”;完成```
【解决方案3】:

我不确定您“每次更改文件时执行一次”或执行“大约一行”的确切含义。 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

【讨论】:

    【解决方案4】:

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

    如果你想在监视器进程读取行时等待 EOF(不管它是什么,例如 tail 或其他任何东西),你必须指定等待间隔,换句话说,什么被认为是“新行”文件。

    想象一下:

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

    因此,如您所见,指定您为“新块”行考虑的时间是绝对必要的。换句话说,如果您在指定的时间间隔内多次到达 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 秒应该没问题,如果不是 - 更改上述脚本的睡眠时间。
    • 以上所有解决方案都可以正常工作
    • 最后:

    不使用 tail 或 while read 的答案将被接受,只要 它们是本机 bash 命令,大约只有一行。

    由于上述原因,没有任何意义。

    希望这会有所帮助。

    【讨论】:

      【解决方案5】:

      如果使用的编辑器没有使用原子保存这个作品

      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)

      【讨论】:

      • 看起来没问题,但是,例如,如果文件在一次操作中更新了 1000 行文本,那么该命令将运行 1000 次。
      • 对我不起作用。每当我将新文本附加到 source_file.js 时,我希望它会打印“在此处执行任何命令”,但是运行该 tail 命令的终端永远不会打印任何内容
      【解决方案6】:

      这将允许您使用tail:

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

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

      您可能可以在循环中使用带有附加逻辑的全局变量来阻止主命令在“x 行和/或 y 秒”内再次运行,以防止该命令针对每个单独的行运行,但这仍然不可靠很快就会变得很麻烦。

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

      [编辑]

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

      【讨论】:

      • 您能解释一下为什么需要两个
      • 简单的答案是第一个“stackoverflow.com/questions/28927162/…
      猜你喜欢
      • 1970-01-01
      • 2017-07-12
      • 1970-01-01
      • 2011-09-22
      • 2015-05-17
      • 1970-01-01
      • 2013-02-22
      • 2015-12-26
      • 2018-03-22
      相关资源
      最近更新 更多