0

I'm trying to write a (sh -bourne shell) script that processes lines as they are written to a file. I'm attempting to do this by feeding the output of tail -f into a while read loop. This tactic seems to be proper based on my research in Google as well as this question dealing with a similar issue, but using bash.

From what I've read, it seems that I should be able to break out of the loop when the file being followed ceases to exist. It doesn't. In fact, it seems the only way I can break out of this is to kill the process in another session. tail does seem to be working fine otherwise as testing with this:

touch file
tail -f file | while read line
do
  echo $line
done

Data I append to file in another session appears just file from the loop processing written above.

This is on HP-UX version B.11.23.

Thanks for any help/insight you can provide!

Community
  • 1
  • 1
AgentConundrum
  • 20,288
  • 6
  • 64
  • 99

5 Answers5

1

If you want to break out, when your file does not exist any more, just do it:

 test -f file || break

Placing this in your loop, should break out.

The remaining problem is, how to break the read line, as this is blocking.

This could you do by applying a timeout, like read -t 5 line. Then every 5 second the read returns, and in case the file does not longer exist, the loop will break. Attention: Create your loop that it can handle the case, that the read times out, but the file is still present.

EDIT: Seems that with timeout read returns false, so you could combine the test with the timeout, the result would be:

  tail -f test.file | while read -t 3 line || test -f test.file; do 
          some stuff with $line
  done
flolo
  • 15,148
  • 4
  • 32
  • 57
0

The following approach backgrounds the tail -f file command, echos its process id plus a custom string prefix (here tailpid: ) to the while loop where the line with the custom string prefix triggers another (backgrounded) while loop that every 5 seconds checks if file is still existing. If not, tail -f file gets killed and the subshell containing the backgrounded while loop exits.

# cf. "The Heirloom Bourne Shell",
# http://heirloom.sourceforge.net/sh.html,
# http://sourceforge.net/projects/heirloom/files/heirloom-sh/ and
# http://freecode.com/projects/bournesh

/usr/local/bin/bournesh -c '
touch file
(tail -f file & echo "tailpid: ${!}" ) | while IFS="" read -r line
do
   case "$line" in
      tailpid:*) while sleep 5; do 
                       #echo hello; 
                       if [ ! -f file ]; then
                          IFS=" "; set -- ${line}
                          kill -HUP "$2"
                          exit
                       fi
                 done & 
                 continue ;;
   esac
   echo "$line"
done
echo exiting ...
'
mazq
  • 1
0

I don't know about HP-UX tail but GNU tail has the --follow=name option which will follow the file by name (by re-opening the file every few seconds instead of reading from the same file descriptor which will not detect if the file is unlinked) and will exit when the filename used to open the file is unlinked:

tail --follow=name test.txt
Robert Gamble
  • 106,424
  • 25
  • 145
  • 137
  • Hi Robert. I did see this option in my Google searches, and it sounded like the best option. Unfortunately, HP-UX doesn't support this option. – AgentConundrum Dec 04 '08 at 04:39
  • @Agent - Can you download and compile (port) GNU tail? It certainly seems to be what you want. – tvanfosson Dec 04 '08 at 04:47
  • @tvanfosson - This is sort of a fun side project to emulate a nice bit of functionality from the system (MPE) we just ported from. It's just to make the devs lives a bit easier. I doubt I could get approval to compile the GNU tail in dev, let alone production (would be a useful tool for oncall work) – AgentConundrum Dec 04 '08 at 04:54
0

Unless you're using GNU tail, there is no way it'll terminate of its own accord when following a file. The -f option is really only meant for interactive monitoring--indeed, I have a book that says that -f "is unlikely to be of use in shell scripts".

But for a solution to the problem, I'm not wholly sure this isn't an over-engineered way to do it, but I figured you could send the tail to a FIFO, then have a function or script that checked the file for existence and killed off the tail if it'd been unlinked.

#!/bin/sh

sentinel ()
{
    while true
    do
        if [ ! -e $1 ]
        then
            kill $2
            rm /tmp/$1
            break
        fi
    done
}       

touch $1

mkfifo /tmp/$1

tail -f $1 >/tmp/$1 &

sentinel $1 $! &

cat /tmp/$1 | while read line
do
    echo $line
done

Did some naïve testing, and it seems to work okay, and not leave any garbage lying around.

Nietzche-jou
  • 14,415
  • 4
  • 34
  • 45
0

I've never been happy with this answer but I have not found an alternative either:

kill $(ps -o pid,cmd --no-headers --ppid $$ | grep tail | awk '{print $1}')

Get all processes that are children of the current process, look for the tail, print out the first column (tail's pid), and kill it. Sin-freaking-ugly indeed, such is life.

rektide
  • 1,492
  • 13
  • 21