4

Is there an easy way to create a pipe that only lasts for a given amount of wall time?

I want to be able to take a "snapshot" of what's being written STDOUT for a period of time (rather than bytes or lines). Something like

tail -f /var/log/whatever.log | pipe_for_seconds 10 > ./10_second_sample_of_log

I'm using BASH, but if this is dramatically easier in some other shell, I'd like to hear about it.

Eli
  • 143

3 Answers3

6

Gnu coreutils since version 7.0 has a timeout command:

timeout 10 tail -f /var/log/whatever.log 

If you really need a pipe to timeout for some slightly different procedure, then pipe into timeout:

tail -f /var/log/whatever.log | timeout 10 cat > ./10_second_sample_of_log

Note though that killing some arbitrary part of a pipeline may cause problems due to buffering, depending on signals and program behaviour (this question covers related issues: Turn off buffering in pipe ). It will usually change the exit code of the process too.

If you don't have (a recent) coreutils, this simple timeout program also works well http://www.unixlabplus.com/unix-prog/timeout/timeout.html or the perl approach:

tail -f /var/log/whatever.log | perl -n -e 'BEGIN{alarm 10; $|=1}; print;'

(Note the $|=1 turns off output buffering, this is to prevent loss of output in the pipeline, as referred to above.)

The (slightly ancient) Netpipes package also has a timelimit command (which you can still find on some Linux systems).

This similar question has a few more options: How to introduce timeout for shell scripting?

mr.spuratic
  • 9,901
4

This should work:

tail -f /var/log/whatever.log > ten_second_sample & sleep 10 && kill $!

Launch tail in background and in parallel sleep 10 seconds and after that kill $! which is the PID of the last launched background process.

GHugo
  • 802
0

Relying on killing PID may not be reliable if tail -f exits too soon (e.g. file does not exist). Another more generic solution, that will also stop the "kill timer" if it is done before the timeout is to use wait and kill %% (%% indicates the most recent job started by bash - using &).

The construction will look like this (in which sleep 2 is any custom command with parameters etc and the timeout is set to 10 seconds):

# Your command that may take some (or not) time to execute:
sleep 2 &
# Set a timer that kills the last started job
sleep 10 & wait -n 1; kill %%; wait -n 1

This construction will wait until either the timeout is reached, or the custom command is done executing (in this case the sleep 2 will always stop first). Then, respectively the command or timeout will be killed with kill %%. Then, the last wait -n 1 blocks further execution until the command or timeout is actually killed (this is optional but likely desired).

Note. Earlier commands executed in parallel will not be affected, as is required and expected.

Example

A more practical example with reading a line from a named pipe with timeout, but for which the timeout-flag of read cannot be used:

# Custom bash function to set timeout in seconds for the most recently started job (parallel execution).
timeout() { sleep $1 & wait -n 1; kill %%; wait -n 1 }

Example 1:

mkfifo /tmp/test read ln </tmp/test & timeout 10

Because there is nothing written to /tmp/test it will timeout after 10 seconds. In order to show that even when the command terminates immediately it will stop the kill timer:

# Example 2:
mkfifo /tmp/test
echo "Hello" >/tmp/test &
ln="$(read ln </tmp/test && echo -e "$ln" & timeout 10)"
echo "Line was read: $ln"

Note with regard to practical use of named pipe. Because read ln is executed in a subshell, it cannot be accessed in the (parent) script. Therefore, the line is printed with echo to store it in a (different) variable ln.

Yeti
  • 187