I am trying to watch a file and execute a command once every time the file is changed, ideally with just native bash commands.
This is as far as I have got, but how do I check if I have reached the beginning or end of the file? I realize that tail -f
doesn't read EOF so how I can tell that I have reached the end of the file?
tail -f source_file.js | while read line || [[ -n "$line" ]];
# how do I execute a command here just **once**?
done
Answers which don't use tail
or while read
will be accepted as long as they are native bash commands and about one line.
Perhaps I could zero a variable every time while is called?
Tail command also comes with an '+' option which is not present in the head command. With this option tail command prints the data starting from specified line number of the file instead of end. For command: tail +n file_name, data will start printing from line number 'n' till the end of the file specified.
To read the entire file, 'cat', 'more', and 'less' commands are used. But when the specific part of the file is required to read then 'head' and 'tail' commands are used to do that task. 'head' command is used to read the file from the beginning and the 'tail' command is used to read the file from the ending.
The watch command is a built-in Linux utility used for running user-defined commands at regular intervals. It temporarily clears all the terminal content and displays the output of the attached command, along with the current system date and time. By default, the watch command updates the output every two seconds.
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
tail
(if you want personally monitor the changes)or
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 script
lastmtime=0
while read -r mtime < <(stat -c '%Z' "$file")
do
if [[ $lastmtime != $mtime ]]
then
lastmtime=$mtime
runcmd "$file"
fi
sleep 1
done
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%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
Principe:
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=
.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; 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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With