有没有一个Shell命令可以延迟缓冲区?

13

我正在寻找一个类似于shell命令X的东西,当我执行以下命令时:

command_a | X 5000 | command_b

command_astdout会在至少5秒后被写入到command_bstdin中。

这是一种延迟缓冲。

据我所知,buffer/mbuffer可以以恒定速率(每秒固定字节数)写入。相反,我需要一个恒定的时间延迟(t=0时,X读取command_a输出块,在t=5000时必须将此块写入command_b)。

[编辑] 我已经实现了它:https://github.com/rom1v/delay


这样的过滤器命令编写起来相当简单,但并不是微不足道的。我不知道是否存在任何现有的工具可以完成这项任务。 - Chris Dodd
时间要求精确吗?最小接受的输入“块”是什么?如果我在时间0输入字节'A',在时间0.7输入字节'B',在时间1.9输入字节'C',输出是否必须精确定时(5.0、5.7和6.9秒)?或者ABC可以在7.0秒输出?缓冲区的最大大小是多少 - 在缓冲区满之前应延迟多少Mb的数据? - grebneke
理想情况下,它们应该被准确地计时(例如在100毫秒内,4.9〜5.1、5.6〜5.8、6.8〜7之间)。字节缓冲区大小应该是另一个参数(X -d 5000 -s 2m)。 - rom1v
你试图通过延迟输入来解决什么问题?也许有更好的解决方法。 - that other guy
我喜欢你的问题。不知何故,它涉及到实时信号处理,比如音频。当时间变得重要时,需要考虑很多因素。你有研究过信号处理/音频工具吗?为了可靠的定时,你不能依赖标准的服务器类型系统,因为I/O和其他事件可能会出现长时间延迟。从“服务器思维”转向“实时思维”是一个(有趣的)挑战。 - grebneke
我很想看到你设置Jack Audio,将你的数据转换为浮点数,并使用Jack将你的数据发送到延迟中,然后提取它并在另一侧进行反向转换。在一个不错的系统上,你可以在10-20毫秒内获得定时。可能不是你要找的,但思考起来很有趣 :) 当然,如果你的数据进来太快,你会遇到缓冲区溢出的问题。算了,只是随便提出一些想法。 - grebneke
5个回答

8

我知道你说你正在寻找一个shell命令,但是你考虑过利用子shell吗?像这样:

command_a | (sleep 5; command_b)

因此,要通过已经使用cat命令打开的文件进行grep操作(我知道,这是很糟糕的cat用法,只是一个例子):

cat filename | (sleep 5; grep pattern)

一个更完整的例子:
$ cat testfile
The
quick
brown
fox
$ cat testfile | (sleep 5; grep brown)
# A 5-second sleep occurs here
brown

甚至按照Michale Kropat的建议,使用带有sleep命令的组合命令也可以工作(并且可能更正确)。像这样:

$ cat testfile | { sleep 5; grep brown; }

注意:不要忘记在你的命令之后加上分号(这里是 grep brown ),因为这是必要的!

3
这只是在开始时提供了5秒的延迟。如果command_b正在比command_a生成数据更快地读取数据,那么它将迅速追上,导致后续从command_a输出的内容没有延迟。 - Chris Dodd
@ChrisDodd 是的,没错。像我举的例子那样,对于非平凡的程序肯定有区别。 - Dan Fego

5

1
你应该给它取一个不那么通用的名称。catdelaypipedelaybuffdelay,... - grebneke

1
类似这样的?
#!/bin/bash
while :
do
   read line 
   sleep 5
   echo $line
done

将文件保存为“slowboy”,然后执行。
chmod +x slowboy

并作为运行

command_a | ./slowboy | command_b

你忘记放置shebang了。 - glglgl
谢谢加一些额外的字符 :-) - Mark Setchell
1
每行延迟5秒。如果command_a输出的行比每5秒一行更快,延迟会变得越来越长... - Chris Dodd

1

这可能会起作用

time_buffered () {
   delay=$1
   while read line; do
       printf "%d %s\n" "$(date +%s)" "$line"
   done | while read ts line; do
       now=$(date +%s)
       if (( now - ts < delay)); then
           sleep $(( now - ts ))
       fi
       printf "%s\n" "$line"
   done
}

commandA | time_buffered 5 | commandB

第一个循环用时间戳标记其输入的每一行,并立即将其传递给第二个循环。第二个循环检查每行的时间戳,并在必要时休眠,直到距离它最初读取的时间过去$delay秒后才输出该行。

是的,但我正在寻找以几Mb/s的速率延迟二进制流。我考虑编码它,但如果已经存在,我会使用它... - rom1v

1
您的问题引起了我的兴趣,我决定回来尝试一下。这是一个基本的Perl实现。它可能不可移植(ioctl),仅在Linux上进行了测试。
基本思路如下:
  • 每X微秒读取可用输入
  • 将每个输入块存储在哈希中,当前时间戳作为键
  • 还将当前时间戳推入队列(数组)
  • 查找队列上最旧的时间戳,并写入+丢弃哈希中的数据(如果延迟足够长)
  • 重复
最大缓冲区大小
存储数据有一个最大大小。如果达到,将不会读取其他数据,直到写入后空间变得可用。
性能
它可能不足以满足您的要求(几MB / s)。我的最大吞吐量为639Kb / s,请参见下文。
测试
# Measure max throughput:
$ pv < /dev/zero | ./buffer_delay.pl > /dev/null

# Interactive manual test, use two terminal windows:
$ mkfifo data_fifo
terminal-one $ cat > data_fifo
terminal-two $ ./buffer_delay.pl < data_fifo

# now type in terminal-one and see it appear delayed in terminal-two.
# It will be line-buffered because of the terminals, not a limitation 
# of buffer_delay.pl

buffer_delay.pl

#!/usr/bin/perl
use strict;
use warnings;
use IO::Select;
use Time::HiRes qw(gettimeofday usleep);
require 'sys/ioctl.ph';

$|++;

my $delay_usec = 3 * 1000000; # (3s) delay in microseconds
my $buffer_size_max = 10 * 1024 * 1024 ; # (10 Mb) max bytes our buffer is allowed to contain.
                              # When buffer is full, incoming data will not be read
                              # until space becomes available after writing
my $read_frequency = 10;      # Approximate read frequency in Hz (will not be exact)

my %buffer;                   # the data we are delaying, saved in chunks by timestamp
my @timestamps;               # keys to %buffer, used as a queue
my $buffer_size = 0;          # num bytes currently in %buffer, compare to $buffer_size_max

my $time_slice = 1000000 / $read_frequency; # microseconds, min time for each discrete read-step

my $sel = IO::Select->new([\*STDIN]);
my $overflow_unread = 0;      # Num bytes waiting when $buffer_size_max is reached

while (1) {
    my $now = sprintf "%d%06d", gettimeofday;  # timestamp, used to label incoming chunks

    # input available?
    if ($overflow_unread || $sel->can_read($time_slice / 1000000)) {

        # how much?
        my $available_bytes;
        if ($overflow_unread) {
            $available_bytes = $overflow_unread;
        }
        else {
            $available_bytes = pack("L", 0);
            ioctl (STDIN, FIONREAD(), $available_bytes);
            $available_bytes = unpack("L", $available_bytes);
        }

        # will it fit?
        my $remaining_space = $buffer_size_max - $buffer_size;
        my $try_to_read_bytes = $available_bytes;
        if ($try_to_read_bytes > $remaining_space) {
            $try_to_read_bytes = $remaining_space;
        }

        # read input
        if ($try_to_read_bytes > 0) {
            my $input_data;
            my $num_read = read (STDIN, $input_data, $try_to_read_bytes);
            die "read error: $!" unless defined $num_read;
            exit if $num_read == 0;       # EOF
            $buffer{$now} = $input_data;  # save input
            push @timestamps, $now;       # save the timestamp
            $buffer_size += length $input_data;
            if ($overflow_unread) {
                $overflow_unread -= length $input_data;
            }
            elsif (length $input_data < $available_bytes) {
                $overflow_unread = $available_bytes - length $input_data;
            }
        }
    }

    # write + delete any data old enough
    my $then = $now - $delay_usec; # when data is old enough
    while (scalar @timestamps && $timestamps[0] < $then) {
        my $ts = shift @timestamps;
        print $buffer{$ts} if defined $buffer{$ts};
        $buffer_size -= length $buffer{$ts};
        die "Serious problem\n" unless $buffer_size >= 0;
        delete $buffer{$ts};
    }

    # usleep any remaining time up to $time_slice
    my $time_left = (sprintf "%d%06d", gettimeofday) - $now;
    usleep ($time_slice - $time_left) if $time_slice > $time_left;
}

请在下方自由发表评论和建议!


啊,我就在同一天实现了它:https://dev59.com/tmEi5IYBdhLWcg3wwegN#21078153 我用我的例子测试了你的实现(特别是网络摄像头流延迟),它运行良好。谢谢! - rom1v
我使用pv测试了我的程序,吞吐量始终等于缓冲区大小除以延迟时间。在相同的参数下(3秒延迟,10Mb缓冲区),我获得了3.33Mb/s的吞吐量:pv < /dev/zero | delay 3s -b10m > /dev/null - rom1v
@rom1v 是的,我也注意到了。但我的 Perl 代码并非如此,它要慢得多,并且不依赖于缓冲区大小,至少在缓冲区足够大的情况下是这样的。在我的系统上,无论缓冲区有多大,速度都会稳定在639Kb/秒。 - grebneke
@romv1 仍然是一个不错的头脑锻炼。我猜当我们老了之后,我们会解决编程测验而不是填字游戏 :) - grebneke

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