Ответ 1
Как казалось, такой команды не существует, я реализовал ее в C: https://github.com/rom1v/delay
delay [-b <dtbufsize>] <delay>
Я ищу команду 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
Как казалось, такой команды не существует, я реализовал ее в C: https://github.com/rom1v/delay
delay [-b <dtbufsize>] <delay>
Я знаю, что вы сказали, что ищете команду оболочки, но как насчет использования подоболочки в ваших интересах? Что-то вроде:
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
), как это необходимо!
Ваш вопрос заинтриговал меня, и я решил вернуться и поиграть с ним. Вот базовая реализация в Perl. Он, вероятно, не переносится (ioctl
), протестирован только на Linux.
Основная идея:
Максимальный размер буфера
Максимальный размер хранимых данных. Если достигнуто, дополнительные данные не будут считаны, пока пространство не станет доступно после записи.
Представление
Это, вероятно, недостаточно быстро для ваших требований (несколько Мбит/с). Моя максимальная пропускная способность была 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;
}
Вы можете оставлять комментарии и предложения ниже!
Что-то вроде этого?
#!/bin/bash
while :
do
read line
sleep 5
echo $line
done
Сохраните файл как "slowboy", затем выполните
chmod +x slowboy
и работать как
command_a | ./slowboy | command_b
Это может сработать
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
секунд после того, как она была впервые прочитана до вывода строки.