Watch file and execute command using tail -f and while read? Watch file and execute command using tail -f and while read? unix unix

Watch file and execute command using tail -f and while read?


From the man:

-f

The -f option causes tail to not stop when end of file is reached, but rather to wait for additional data to be appended to the input.

So, if you want monitor and do actions, need break the script into two processes

  • one will show the content tail (if you want personally monitor the changes)
  • second will monitor changes and do actions

or

  • you should use for example perl or python what can monitor end-of-file and execute some actions when reach it (for example, run an bash script).

The bash soultion can be based of file-modification time

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

added another solution based on standard perl

perltail() {#adapted from the perlfaq5#http://perldoc.perl.org/perlfaq5.html#How-do-I-do-a-tail--f-in-perl%3fperl -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 linedo    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

Principe:

  • the perltail bash function runs an perl implementation of tail -f, and additionally to, when reached the end-of-file it prints an MARK to the output, here: =EOF-reached=.
  • the bash while read looking for the MARK and run action only the the mark exists - e.g. only when the end-of-file reached.


It's by no means a native bash solution but you could use libinotify to do what you want:

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

This watches for modifications to file and performs the action within the loop whenever they happen. The -qq switch suppresses the output of the program, which by default prints a message every time something happens to the file.


I am not sure of your exact meaning of doing something "once every time a file is changed" or an implementation of "about one line". The tail command is usually used in a line-oriented manner, so I guess that something like 500 lines of text appended to a file in a single write(2) is an update worthy of only one invocation of you command.

But what about, say tens of lines appended after delays of tens of milliseconds? How often do you wish the command be called?

If libinotify is available, per Mr. Fenech, use it. If you're trying to use somewhat more basic shell utilities, find(1) can also be used to notice when one file has become newer than another.

For example, a script that watches a file, and runs a supplied command when it's updated, polling at 1-second intervals.

#!/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; shiftFTMP=$(mktemp /tmp/$(basename "$FILE")_tsref.XXXXXX)trap 'rm -f "$FTMP"' EXITtouch -r "$FILE" "$FTMP"while true; do    FOUT=$(find "$FILE" -newer "$FTMP") || exit $?    if [ "$FOUT" = "$FILE" ]; then        touch -r "$FILE" "$FTMP"        eval "$@"    else        sleep 1    fidone