实时删除Shell中的回车符

5

为了方便批量处理,我试图创建一个shell脚本来简化ffmpeg的实时控制台输出,只显示当前正在编码的帧。我的最终目标是在某种进度指示器中使用这些信息。

对于那些不熟悉ffmpeg输出的人,它将编码视频信息输出到stdout,并将控制台信息输出到stderr。当它真正开始显示编码信息时,它使用回车符来防止控制台屏幕被填满。这使得无法仅使用grep和awk来捕获适当的行和帧信息。

我尝试的第一件事是使用tr替换回车符:

$ ffmpeg -i "ScreeningSchedule-1.mov" -y "test.mp4" 2>&1 | tr '\r' '\n'

这样可以在控制台上实时显示输出。然而,如果我将该信息管道传输到grep或awk或其他任何内容,tr的输出将被缓冲并且不再实时。例如:$ ffmpeg -i "ScreeningSchedule-1.mov" -y "test.mp4" 2>&1 | tr '\r' '\n'>log.txt会导致文件立即被填充一些信息,然后5-10秒后,更多行会被放入日志文件中。

起初我认为sed非常适合这个问题:$ # ffmpeg -i "ScreeningSchedule-1.mov" -y "test.mp4" 2>&1 | sed 's/\\r/\\n/',但它到达所有回车符的那一行并等待处理完成之前不会尝试执行任何操作。我认为这是因为sed按行处理,并且需要整行完成才能执行其他操作,然后它也不会替换回车符。我尝试了各种不同的正则表达式来替换回车符和换行符,但还没有找到替换回车符的解决方案。我正在运行OSX 10.6.8,因此我使用的是BSD sed,这可能是原因。

我还尝试将信息写入日志文件并使用tail -f读取它,但我仍然遇到实时替换回车符的问题。

我看到有python和perl的解决方案,但我不愿意立即采用这种方法。首先,我不懂python或perl。其次,我有一个完全功能的批处理shell应用程序,我需要将其移植或弄清楚如何与python / perl集成。这可能不难,但不是我想要做的,除非我绝对必须这样做。因此,我正在寻找一个shell解决方案,最好是bash,但任何OSX shell都可以。

如果我想要的根本无法做到,那么我想我将跨越这座桥时再考虑。

3个回答

5
如果只是在管道之后接收应用程序的输出缓冲问题,那么你可以尝试使用 gawk(以及一些BSD awk)或 mawk 来刷新缓冲区。例如,尝试执行以下命令:
... | gawk '1;{fflush()}' RS='\r\n' > log.txt

如果您的awk不支持此功能,您可以通过重复关闭输出文件并追加下一行来强制执行此操作...

... | awk '{sub(/\r$/,x); print>>f; close(f)}' f=log.out

或者你可以直接使用shell,例如在bash中:

... | while IFS= read -r line; do printf "%s\n" "${line%$'\r'}"; done > log.out

非常感谢!您的第一个命令完美地运行了,当然,因为我在OSX上,我只是使用了awk。这节省了我很多麻烦! - Seth
1
有一件事要补充到这个很好的答案中:检查您安装的awk/gawk/mawk文档以了解如何解释RS。在我的本地OSX盒子上,awk对记录分隔符有一个隐含的OR(回车或换行)。在我的Ubuntu服务器上,如果RS有多个字符,gawk 4.0.1将解释RS为正则表达式。因此,我必须使用RS='\r|\n'来实现我在OSX上看到的相同行为。 - ajmicek

4

当stdout和stderr连接到终端时,libc使用行缓冲,并且当连接到管道时使用全缓冲(具有4KB缓冲区)。这发生在生成输出的进程中,而不是接收进程——在你的情况下,这是ffmpeg的问题,而不是tr的问题。

unbuffer ffmpeg -i "ScreeningSchedule-1.mov" -y "test.mp4" 2>&1 | tr '\r' '\n'
stdbuf -e0 -o0 ffmpeg -i "ScreeningSchedule-1.mov" -y "test.mp4" 2>&1 | tr '\r' '\n'

尝试使用 unbuffer or stdbuf 来禁用输出缓冲。


我对此有点困惑。如果我只是将stderr导向文件,即2>log.txt,我会得到相对实时的文件更新(如果我在文件上使用tail -f,我会看到我认为是行缓冲更新;比tr正在执行的缓冲要快得多)。就“unbuffer”和“stdbuf”而言,我似乎在我的系统上没有它们,尽管我有“expect”,这让我想到我应该有“unbuffer”。我确实希望我的脚本具有一定的可移植性,因此如果我需要使用非标准应用程序,我希望有一个静态可执行文件可以随我的脚本一起旅行。 - Seth
好的,我尝试了通过按照这里的说明[http://superuser.com/questions/59497/writing-tail-f-output-to-another-file]自己制作`unbuffer`。但仍然只能获得缓冲块中的更新,而不是逐行更新。 - Seth

1

在管道中进程之间的数据缓冲受到一些系统限制的控制,在我的系统(Fedora 17)上,这些限制是不可修改的:

$ ulimit -a | grep pipe
pipe size            (512 bytes, -p) 8
$ ulimit -p 1
bash: ulimit: pipe size: cannot modify limit: Invalid argument
$ 

尽管此缓冲区大多与生产者在消费者不以相同速度消耗时被允许生产多少多余数据之前停止有关,但它也可能影响较小数据量的交付时间(对此并不确定)。
这是管道数据的缓冲区,我认为这里没有太多可以调整的。然而,读取/写入管道数据的程序可能会缓冲标准输入/输出数据,您需要避免这种情况。
这是一个 Perl 脚本,应该可以进行最小输入缓冲和无输出缓冲的翻译:
#!/usr/bin/perl
use strict;
use warnings;

use Term::ReadKey;
$ReadKeyTimeout = 10; # seconds

$| = 1; # OUTPUT_AUTOFLUSH

while( my $key = ReadKey($ReadKeyTimeout) ) {
        if ($key eq "\r") {
                print "\n";
                next;
        }
        print $key;
}

然而,如前所述,如果您想要实时响应,则应确保ffmpeg不会缓冲其输出。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接