简体   繁体   English

在子shell中使用tail和while / break不会退出循环

[英]Using tail in a subshell in conjunction with while/break does not exit the loop

I have been facing a very peculiar issue with shell scripts. 我一直面临着shell脚本的一个非常奇怪的问题。

Here is the scenario 这是场景

Script1 (spawns in background)--> Script2 Script1(在后台生成) - > Script2

Script2 has the following code Script2具有以下代码

function check_log()
{
    logfile=$1
    tail -5f ${logfile} | while read line
    do
      echo $line
      if echo $line|grep "${triggerword}";then
        echo "Logout completion detected"
        start_leaks_detection
        triggerwordfound=true
        echo "Leaks detection complete"
      fi
      if $triggerwordfound;then
        echo "Trigger word found and processing complete.Exiting"
        break
      fi

    done
        echo "Outside loop"
        exit 0

}

check_log "/tmp/somefile.log" "Logout detected"

Now the break in while loop does not help here. 现在,while循环中断并没有帮助。 I can see "Logout completion detected" as well as "Leaks detection complete" being echoed on the stdout, but not the string "outside loop" 我可以看到“检测到注销完成”以及“泄漏检测完成”在标准输出上回显,但不是字符串“外部循环”

I am assuming this has to do something with tail -f creating a subshell. 我假设这必须用tail -f创建一个子shell。 What I want to do is, exit that subshell as well as exit Script2 to get control back to Script1. 我想要做的是,退出子shell并退出Script2以控制回Script1。

Can someone please shed some light on how to do this? 有人可以说明如何做到这一点吗?

Instead of piping into your while loop, use this format instead: 而不是管道进入你的while循环,而是使用这种格式:

while read line
do
   # put loop body here
done < <(tail -5f ${logfile})

Try this, although it's not quite the same (it doesn't skip the beginning of the log file at startup): 试试这个,虽然它不完全相同(它不会在启动时跳过日志文件的开头):

triggerwordfound=
while [ -z "$triggerwordfound" ]; do
    while read line; do
        echo $line
        if echo $line|grep "${triggerword}";then
            echo "Logout completion detected"
            start_leaks_detection
            triggerwordfound=true
            echo "Leaks detection complete"
        fi
    done
done < "$logfile"
echo "Outside loop"

The double loop effectively does the same thing as tail -f . 双循环有效地与tail -f做同样的事情。

Your function works in a sense, but you won't notice that it does so until another line is written to the file after the trigger word has been found. 从某种意义上说,您的函数是有效的,但是在找到触发器字之后,在将另一行写入文件之前,您不会注意到它。 That's because tail -5 -f can usually write all of the last five lines of the file to the pipe in one write() call and continue to write new lines all in one call, so it won't be sent a SIGPIPE signal until it tries to write to the pipe after the while loop has exited. 这是因为tail -5 -f通常可以在一次write()调用中将文件的最后五行write()管道并继续在一次调用中写入新行,因此直到它才会发送SIGPIPE信号直到它会在while循环退出尝试写入管道。

So, if your file grows regularly then there shouldn't be a problem, but if it's more common for your file to stop growing just after the trigger word is written to it, then your watcher script will also hang until any new output is written to the file. 因此,如果您的文件经常增长,那么应该没有问题,但如果您的文件在写入触发器字后立即停止增长更常见,那么您的观察器脚本也会挂起,直到写入任何新输出到文件。

Ie SIGPIPE is not sent immediately when a pipe is closed, even if there's un-read data buffered in it, but only when a subsequent write() on the pipe is attempted. 即管道关闭时,即使在其中缓存了未读取的数据,也不会立即发送SIGPIPE ,但仅在尝试管道上的后续write()时才发送。

This can be demonstrated very simply. 这可以非常简单地证明。 This command will not exit (provided the tail of the file is less than a pipe-sized buffer) until you either interrupt it manually, or you write one more byte to the file: 此命令不会退出(假设文件的尾部小于管道大小的缓冲区),直到您手动中断它,或者再向文件写入一个字节:

tail -f some_large_file | read one

However if you force tail to make multiple writes to the pipe and make sure the reader exits before the final write, then everything will work as expected: 但是,如果强制tail对管道进行多次写入并确保读取器在最终写入之前退出,那么一切都将按预期工作:

tail -c 1000000 some_large_file | read one

Unfortunately it's not always easy to discover the size of a pipe buffer on a given system, nor is it always possible to only start reading the file when there's already more than a pipe buffer's worth of data in the file, and the trigger word is already in the file and at least a pipe buffer's size bytes from the end of the file. 不幸的是,在给定系统上发现管道缓冲区的大小并不总是很容易,也不总是只能在文件中已经存在多个管道缓冲区的数据时才开始读取文件,并且触发字已经是在文件中,至少是文件末尾的管道缓冲区大小字节。

Unfortunately tail -F (which is what you should probably use instead of -f ) doesn't also try writing zero bytes every 5 seconds, or else that would maybe solve your problem in a more efficient manner. 不幸的是, tail -F (你应该使用它而不是-f )也不会尝试每5秒写一个零字节,否则这可能会以更有效的方式解决你的问题。

Also, if you're going to stick with using tail , then -1 is probably sufficient, at least for detecting any future event. 此外,如果您要坚持使用tail ,那么-1可能就足够了,至少对于检测任何未来事件而言。

BTW, here's a slightly improved implementation, still using tail since I think that's probably your best option (you could always add a periodic marker line to the log with cron or similar (most syslogd implementations have a built-in mark feature too) to guarantee that your function will return within the period of the marker): 顺便说一句,这是一个稍微改进的实现,仍然使用tail因为我认为这可能是你最好的选择(你总是可以用cron或类似的方法在日志中添加一个周期标记行(大多数syslogd实现也有内置的标记功能)来保证您的函数将在标记期间返回:

check_log ()
{
        tail -1 -F "$1" | while read line; do
                case "$line" in
                *"${2:-SOMETHING_IMPOSSIBLE_THAT_CANNOT_MATCH}"*)
                        echo "Found trigger word"
                        break
                        ;;
                esac
        done
}

Replace the echo statement with whatever processing you need to do when the trigger phrase is read. echo语句替换为读取触发器短语时需要执行的任何处理。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM