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
orpython
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 oftail -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