Есть ли команда оболочки для задержки буфера?

Я ищу команду X оболочки, например, когда я выполняю:

command_a | X 5000 | command_b

stdout command_a записывается в stdin команды command_b (по крайней мере) через 5 секунд.

Это своего рода буфер задержки.

Насколько я знаю, buffer/mbuffer может писать с постоянной скоростью (фиксированное количество байтов в секунду). Вместо этого мне нужна постоянная задержка во времени (t = 0, когда X читает выходной блок command_a, при t = 5000 он должен записать этот кусок в command_b).

[edit] Я внедрил это: https://github.com/rom1v/delay

Ответы

Ответ 1

Как казалось, такой команды не существует, я реализовал ее в C: https://github.com/rom1v/delay

delay [-b <dtbufsize>] <delay>

Ответ 2

Я знаю, что вы сказали, что ищете команду оболочки, но как насчет использования подоболочки в ваших интересах? Что-то вроде:

command_a | (sleep 5; command_b)

Так что grep файл cat -ed через (я знаю, я знаю, плохое использование 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

Ваш вопрос заинтриговал меня, и я решил вернуться и поиграть с ним. Вот базовая реализация в Perl. Он, вероятно, не переносится (ioctl), протестирован только на Linux.

Основная идея:

  • читать доступный вход каждые X микросекунд
  • хранить каждый входной блок в хеше, с текущей меткой времени в качестве ключа
  • также нажмите текущую временную метку в очереди (массив)
  • искать самые старые временные метки в очереди и писать + удалять данные из хеша, если задержка достаточно длинная
  • повторение

Максимальный размер буфера

Максимальный размер хранимых данных. Если достигнуто, дополнительные данные не будут считаны, пока пространство не станет доступно после записи.

Представление

Это, вероятно, недостаточно быстро для ваших требований (несколько Мбит/с). Моя максимальная пропускная способность была 639 Kb/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;
}

Вы можете оставлять комментарии и предложения ниже!

Ответ 4

Что-то вроде этого?

#!/bin/bash
while :
do
   read line 
   sleep 5
   echo $line
done

Сохраните файл как "slowboy", затем выполните

chmod +x slowboy

и работать как

command_a | ./slowboy | command_b

Ответ 5

Это может сработать

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 секунд после того, как она была впервые прочитана до вывода строки.