在 bash 脚本仍在运行时强制将输出刷新到文件

Posted

技术标签:

【中文标题】在 bash 脚本仍在运行时强制将输出刷新到文件【英文标题】:Force flushing of output to a file while bash script is still running 【发布时间】:2010-11-28 15:12:38 【问题描述】:

我有一个小脚本,每天由 crontab 使用以下命令调用:

/homedir/MyScript &> some_log.log

这种方法的问题是 some_log.log 仅在 MyScript 完成后创建。我想在程序运行时将程序的输出刷新到文件中,这样我就可以做类似

的事情
tail -f some_log.log

并跟踪进度等

【问题讨论】:

我们需要一个描述——或者如果可能的话——代码——你的小脚本到底做什么...... 要取消缓冲 python 脚本,您可以使用“python -u”。要取消缓冲 perl 脚本,请参阅下面的 Greg Hewgill 回复。等等…… 如果可以编辑脚本,通常可以在脚本中显式刷新输出缓冲区,例如在 python 中使用sys.stdout.flush() 【参考方案1】:

我找到了解决这个here 的方法。使用 OP 的示例,您基本上可以运行

stdbuf -oL /homedir/MyScript &> some_log.log

然后在每行输出后刷新缓冲区。我经常将它与nohup 结合起来在远程机器上运行长时间的作业。

stdbuf -oL nohup /homedir/MyScript &> some_log.log

这样,当您注销时,您的进程不会被取消。

【讨论】:

您能否为stdbuf 添加一些文档的链接?基于this comment,它似乎在某些发行版上不可用。你能澄清一下吗? stdbuf -o 调整标准输出缓冲。其他选项是 -i 和 -e 用于标准输入和标准错误。 L 设置行缓冲。也可以指定缓冲区大小,或 0 表示无缓冲。 该链接不再可用。 @NicHartley:stdbuf 是 GNU coreutils 的一部分,documentation can be found at gnu.org 如果它可以帮助任何人,use export -f my_function 然后stdbuf -oL bash -c "my_function -args" 如果您需要运行函数而不是脚本【参考方案2】:
script -c <PROGRAM> -f OUTPUT.txt

键是-f。引用 man 脚本:

-f, --flush
     Flush output after each write.  This is nice for telecooperation: one person
     does 'mkfifo foo; script -f foo', and another can supervise real-time what is
     being done using 'cat foo'.

在后台运行:

nohup script -c <PROGRAM> -f OUTPUT.txt

【讨论】:

哇!适用于busybox 的解决方案! (之后我的外壳冻结了,但无论如何) -c 是干什么用的? 来自man script: -c, --command command 运行命令而不是交互式shell。这使得脚本可以很容易地捕获当 stdout 不是 tty 时行为不同的程序的输出。另一个有用的参数是-q,可以像这样与-c 结合使用:-qc。这可以防止标准输出的开始和完成消息。【参考方案3】:

bash 本身永远不会真正将任何输出写入您的日志文件。相反,它作为脚本的一部分调用的命令将各自单独写入输出并在需要时刷新。所以你的问题实际上是如何强制 bash 脚本中的命令刷新,这取决于它们是什么。

【讨论】:

这个答案我实在看不懂。 要更好地了解标准输出为什么会这样,请查看***.com/a/13933741/282728。一个简短的版本——默认情况下,如果重定向到一个文件,stdout 是完全缓冲的;仅在刷新后才将其写入文件。 Stderr 不是——它写在每个 '\n' 之后。一种解决方案是使用下面 user3258569 推荐的“脚本”命令,在每行结束后刷新标准输出。 陈述显而易见,十年后,但这是一个评论,而不是一个答案,它不应该是公认的答案。 这也不是准确的答案。即使标准输出命令在标准错误命令之前,标准输出仍然可以在标准错误之后。所以它与depends on what they are 无关,因为它们已经依赖于 bash 和 bash 脚本本身之外的其他东西。【参考方案4】:

您可以使用tee 写入文件而无需刷新。

/homedir/MyScript 2>&1 | tee some_log.log > /dev/null

【讨论】:

这仍然缓冲输出,至少在我的 Ubuntu 18.04 环境中是这样。内容最终会以任何一种方式写入文件,但我认为 OP 正在寻求一种方法,在该方法中他们可以在文件完成写入之前更准确地监控进度,并且这种方法只允许输出重定向是的。【参考方案5】:

这不是bash 的功能,因为shell 所做的只是打开有问题的文件,然后将文件描述符作为脚本的标准输出传递。您需要做的是确保从您的脚本中刷新输出的频率比您现在要高。

例如,在 Perl 中,这可以通过设置来完成:

$| = 1;

有关更多信息,请参阅perlvar。

【讨论】:

【参考方案6】:

这会有帮助吗?

tail -f access.log | stdbuf -oL cut -d ' ' -f1 | uniq 

这将使用stdbuf utility 立即显示来自 access.log 的唯一条目。

【讨论】:

唯一的麻烦是 stdbuf 似乎是一些旧的实用程序,在新发行版上不可用。 ..也不在我的忙箱中:( 其实我现在在 Ubuntu 中有 stdbuf,不知道我从哪里得到的。 我在 Centos 7.5 中有 stdbuf 在 Ubuntu 18.04 中,stdbufcoreutilus 的一部分(与 apt-file search /usr/bin/stdbuf 一起找到)。【参考方案7】:

输出的缓冲取决于你的程序/homedir/MyScript是如何实现的。如果你发现输出被缓冲了,你必须在你的实现中强制它。例如,如果是 python 程序,则使用 sys.stdout.flush();如果是 C 程序,则使用 fflush(stdout)。

【讨论】:

【参考方案8】:

刚刚发现here 的问题是您必须等待从脚本运行的程序完成它们的工作。 如果在您的脚本中您在 background 中运行程序,您可以尝试更多。

一般来说,在您退出之前调用sync 可以刷新文件系统缓冲区并且可以提供一点帮助。

如果在脚本中您在后台 (&amp;) 中启动一些程序,您可以wait 让它们在您退出脚本之前完成。要了解它的功能,您可以在下面看到

#!/bin/bash
#... some stuffs ...
program_1 &          # here you start a program 1 in background
PID_PROGRAM_1=$!   # here you remember its PID
#... some other stuffs ... 
program_2 &          # here you start a program 2 in background
wait $!            # You wait it finish not really useful here
#... some other stuffs ... 
daemon_1 &           # We will not wait it will finish
program_3 &          # here you start a program 1 in background
PID_PROGRAM_3=$!   # here you remember its PID
#... last other stuffs ... 
sync
wait $PID_PROGRAM_1
wait $PID_PROGRAM_3  # program 2 is just ended
# ...

由于wait 与作业以及PID 数字一起使用,因此应该将懒惰的解决方案放在脚本的末尾

for job in `jobs -p`
do
   wait $job 
done

如果你在后台运行一些其他的东西,情况会更加困难,因为你必须搜索并等待(如果是的话)所有 child 进程的结束:例如,如果你运行一个 daemon 可能不是等待它完成的情况:-)。

注意:

wait $! 表示“等待最后一个后台进程完成”,其中$! 是最后一个后台进程的 PID。所以把wait $!放在program_2 &amp;之后就相当于直接执行program_2而不用&amp;在后台发送它

wait的帮助下:

Syntax    
    wait [n ...]
Key  
    n A process ID or a job specification

【讨论】:

【参考方案9】:

感谢@user3258569,脚本可能是busybox 中唯一有效的东西!

不过,在它之后,贝壳对我来说是冰冻的。查找原因,我在script manual page 中发现了这些大红色警告“不要在非交互式 shell 中使用”:

script 主要用于交互式终端会话。什么时候 stdin 不是终端(例如:echo foo | script),则 session 可以挂起,因为脚本内的交互式 shell 会话错过 EOF 并且script 不知道何时关闭会话。 有关详细信息,请参阅注意部分。

没错。 script -c "make_hay" -f /dev/null | grep "needle" 为我冻结了外壳。

与警告相反,我认为echo "make_hay" | script 会通过 EOF,所以我尝试了

echo "make_hay; exit" | script -f /dev/null | grep 'needle'

它成功了!

注意手册页中的警告。这可能不适合你。

【讨论】:

【参考方案10】:

stdbuf 的替代品是awk 'print END fflush()' 我希望有一个内置的 bash 来执行此操作。 通常不需要,但对于旧版本,文件描述符上可能存在 bash 同步错误。

【讨论】:

【参考方案11】:

我在 Mac OS X 中使用 StartupItems 的后台进程遇到了这个问题。我就是这样解决的:

如果我创建sudo ps aux,我可以看到mytool 已启动。

我发现(由于缓冲)当 Mac OS X 关闭时 mytool 永远不会将输出传输到 sed 命令。但是,如果我执行sudo killall mytool,那么mytool 会将输出传输到sed 命令。因此,我在 Mac OS X 关闭时执行的 StartupItems 中添加了一个 stop 案例:

start)
    if [ -x /sw/sbin/mytool ]; then
      # run the daemon
      ConsoleMessage "Starting mytool"
      (mytool | sed .... >> myfile.txt) & 
    fi
    ;;
stop)
    ConsoleMessage "Killing mytool"
    killall mytool
    ;;

【讨论】:

这确实不是一个好的答案 Freeman,因为它非常特定于您的环境。 OP 想要监控输出而不是杀死它。【参考方案12】:

我不知道它是否会起作用,但是打电话给sync 怎么样?

【讨论】:

sync 是低级文件系统操作,与应用程序级别的缓冲输出无关。 sync 如有必要,将任何脏文件系统缓冲区写入物理存储。这是操作系统内部的;无论磁盘块是否已写入物理存储,运行在操作系统之上的应用程序始终可以看到文件系统的一致视图。对于原始问题,应用程序(脚本)可能正在将输出缓冲在应用程序内部的缓冲区中,并且操作系统甚至不知道(还)输出实际上注定要写入标准输出。因此,假设的“同步”类型操作将无法“进入”脚本并提取数据。【参考方案13】:

不管你喜不喜欢,这就是重定向的工作原理。

在您的情况下,您的脚本的输出(意味着您的脚本已完成)重定向到该文件。

您要做的是在脚本中添加这些重定向。

【讨论】:

以上是关于在 bash 脚本仍在运行时强制将输出刷新到文件的主要内容,如果未能解决你的问题,请参考以下文章

通过bash将输出重定向到文件[重复]

Bash脚本函数溢出到其他脚本中

如何从输出到前景的Bash脚本运行无限循环

如何强制客户端刷新 JavaScript 文件?

将 *unbuffered* 标准输出从 bash 脚本本身复制到文件

在 bash 脚本中执行时未生成 gprof 输出