Bash 外壳中回车的实时清除

Bash 外壳中回车的实时清除,bash,shell,ffmpeg,carriage-return,Bash,Shell,Ffmpeg,Carriage Return,对于上下文,我试图创建一个shell脚本,简化ffmpeg的实时控制台输出,只显示正在编码的当前帧。我的最终目标是在批处理的某种进度指示器中使用这些信息 对于那些不熟悉ffmpeg输出的人,它将编码的视频信息输出到stdout,并将控制台信息输出到stderr。此外,当它实际显示编码信息时,它使用回车键来防止控制台屏幕被填满。这使得不可能简单地使用grep和awk来捕获适当的线和帧信息 我尝试的第一件事是使用tr替换回车符: $ffmpeg-i“ScreeningSchedule-1.mov”-

对于上下文,我试图创建一个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的/\\r/\\n/'
,但它到达包含所有回车符的行,并等待处理完成后再尝试执行任何操作。我认为这是因为sed是逐行工作的,需要在执行任何其他操作之前完成整行,然后它不会替换回车。我已经尝试了各种不同的正则表达式,用于回车和换行,但还没有找到替代回车的解决方案。我运行的是OSX10.6.8,所以我使用的是BSDSED,这可能就是原因

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

我已经在python和perl中看到了这方面的解决方案,但是,我不愿意立即走这条路。首先,我不懂python或perl。第二,我有一个功能齐全的批处理shell应用程序,我需要它来移植或研究如何与python/perl集成。也许不难,但不是我想进入的领域,除非我必须这样做。所以我正在寻找一个shell解决方案,最好是bash,但是任何OSX shell都可以


如果我想要的根本不可行,那么我想我会在到达那里时跨越这座桥。

Libc在stdout和stderr连接到终端时使用行缓冲,在连接到管道时使用全缓冲(带有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'

尝试使用禁用输出缓冲。

管道中进程之间的数据缓冲由一些系统限制控制,至少在我的系统(Fedora 17)上无法修改:

$ ulimit -a | grep pipe
pipe size            (512 bytes, -p) 8
$ ulimit -p 1
bash: ulimit: pipe size: cannot modify limit: Invalid argument
$ 
尽管这种缓冲主要与生产者在停止之前允许产生多少多余数据有关(如果消费者没有以相同的速度消费),但它也可能影响少量数据的交付时间(对此不太确定)

这就是管道数据的缓冲,我认为这里没有太多需要调整的地方。但是,读取/写入管道数据的程序也可能会缓冲stdin/stdout数据,这是您希望在本例中避免的

下面是一个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不会缓冲其输出。

如果这只是接收应用程序在管道之后缓冲输出的问题。然后您可以尝试使用
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

我对此有点困惑。如果我只是通过管道将stderr传输到一个文件,即
2>log.txt
,我会得到文件的相对实时更新(如果我在文件上使用
tail-f
,我会看到我假设的行缓冲更新;比缓冲
tr
要快得多)。就
unbuffer
stdbuf
而言,我的系统上似乎没有这两个选项,尽管我确实有
expect
,这让我觉得我应该有
unbuffer
。我确实希望我的脚本具有一些可移植性,因此如果我需要使用非标准应用程序,我希望有一个可以随脚本一起运行的静态可执行文件。好的,我尝试了通过自己根据说明来解除缓冲。这仍然只能在缓冲块中得到更新,而不是通过行。非常感谢!您的第一个命令工作得很好,当然我只是使用了
awk
,因为我在OSX上。这让我省去了那么多头痛!这个伟大的答案需要补充一点:检查您安装的awk/gawk/mawk文档,了解RS的解释方式。在我的本地OSX框中,awk有一个用于记录分隔符的隐式OR(回车符或换行符)。在我的Ubuntu服务器上,如果RS有多个字符,Gawk4.0.1会将其解释为正则表达式。因此,我必须使用
RS='\r |\n'
来实现我在OSX上看到的相同行为。