How to timeout a tail pipeline properly on shell

偶尔善良 提交于 2020-02-06 08:44:16

问题


I am implementing monitor_log function which will tail the most recent line from running log and check required string with while loop, the timeout logic should be when the tail log running over 300 seconds, it must close the tail and while loop pipeline.

The big issue i found is for some server the running log NOT keep generating, which means tail -n 1 -f "running.log" will also NOT generate output for while loop to consume, hence the timeout checking logic if [[ $(($SECONDS - start_timer)) -gt 300 ]] will not hit properly.

e.g I set 300 seconds to timeout, but if running.log stopped generate new line before 300 seconds and no more new line in 30 minutes, tail will not generate new output in 30 minutes, hence timeout checking logic in while loop not hit in 30 minutes, so even after 300 seconds it keep tailing and not break out, and if no new line coming from running.log forever, the timeout checking logic will not hit forever.

function monitor_log() {
  if [[ -f "running.log" ]]; then
    # Timer start
    start_timer=$SECONDS
    # Tail the running log last line and keep check required string
    tail -n 1 -f "running.log" | while read tail_line
    do
      if [[ $(($SECONDS - start_timer)) -gt 300 ]]; then
        break;
      fi
      if [[ "$tail_line" == "required string" ]]; then
        capture_flag=1
      fi
      if [[ $capture_flag -eq 1 ]]; then
        break;
      fi
    done
  fi
}

Could you help to figure out the proper way to timeout the tail and while loop when 300 seconds ? Thank you.


回答1:


Two options worth considering for inactivity timeout. Usually, option #1 works better.

Option 1: Use timeout (read -t timeout).

It will cap the the 'read' time. See information from bash man. The timeout will cause the read to fail, breaking the whlie loop.

In the code above, replace

tail -n 1 -f "running.log" | while read tail_line

with

 tail -n 1 -f "running.log" | while read -t 300 tail_line

Option 2: TMOUT envvar

It's possible to get same effect by setting TMOUT env var.

From bash man - 'read' command:

-t timeout

Cause read to time out and return failure if a complete line of input (or a specified number of characters) is not read within timeout seconds. timeout may be a decimal number with a fractional portion following the decimal point. This option is only effective if read is reading input from a terminal, pipe, or other special file; it has no effect when reading from regular files. If read times out, read saves any partial input read into the specified variable name. If timeout is 0, read returns immediately, without trying to read any data. The exit status is 0 if input is available on the specified file descriptor, non-zero otherwise. The exit status is greater than 128 if the timeout is exceeded.




回答2:


Based on dash-o's answer I did test for option 1, the -t for read command works fine only when while read loop on main shell and tail in sub shell, in my question, the tail in main shell, and while read loop consume its output in subshell, in this condition, even setup -t for read command, script not stop when time used up. Refer to Monitoring a file until a string is found, Bash tail -f with while-read and pipe hangs and How to [constantly] read the last line of a file?

The working code based on dash-o's solution below:

function monitor_log() {
  if [[ -f "running.log" ]]; then
    # Tail the running log last line and keep check required string
    while read -t 300 tail_line
    do
      if [[ "$tail_line" == "required string" ]]; then
        capture_flag=1
      fi
      if [[ $capture_flag -eq 1 ]]; then
        break;
      fi
    done < <(tail -n 1 -f "running.log")
    # Silently kill the remained tail process
    tail_pid=$(ps -ef | grep 'tail' | cut -d' ' -f5)
    kill -13 $tail_pid
  fi
}

But as test, this function after timeout auto terminate will left tail process alive, we can observe PID by check ps -ef on console, need to kill tail_PID separately.

Also test another solution: not change tail and while read loop position, so tail still on main shell and while read loop keep in sub shell after | pipeline, the only change is adding GNU's timeout command before tail command, it works perfect and no tail process left after timeout auto terminate:

function monitor_log() {
  if [[ -f "running.log" ]]; then
    # Tail the running log last line and keep check required string
    timeout 300 tail -n 1 -f "running.log" | while read tail_line
    do
      if [[ "$tail_line" == "required string" ]]; then
        capture_flag=1
      fi
      if [[ $capture_flag -eq 1 ]]; then
        break;
      fi
    done
  fi
}


来源:https://stackoverflow.com/questions/59417728/how-to-timeout-a-tail-pipeline-properly-on-shell

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!