Как отключить, как часто передаются строки вывода?

желание

Я хочу повторно запускать команду в ответ на строки, переданные по ней:

firehose | expensive-command 

Тем не менее, я получаю много строк, и команда ресурсоемкая. Я хочу, чтобы вход для команды был отфильтрован, чтобы он выполнялся не более одного раза каждые x секунд:

 firehose | interval 1 second | expensive-command 

Команда interval должна быть не просто фильтром. Он должен отправить самую последнюю полученную строку в конце периода кулдауна, если она есть, а не просто заблокировать все, что прибывает во время кулдауна.

Как я могу это сделать?


попытка

 epoch () { date +%s --date="$*" } interval () { INTERVAL="$*" LAST_RUN_AT=0 WHEN_TO_RUN=0 while read LINE; do if (( $(epoch now) >= $WHEN_TO_RUN )) then echo $LINE WHEN_TO_RUN="$(epoch now + $INTERVAL)" fi done } alias firehose='(print "1\n2\n3" ; sleep 2 ; print "4\n")' alias expensive-command='cat' firehose | interval 1 second | expensive-command 

В основном это работает, но проблема состоит в том, что он не может отложить прохождение линий до тех пор, пока не появится – он может принять решение немедленно передать их или оставить их.

Что происходит:

 1 4 

Дроссель получает 1 и передает его, затем возобновляет кулдаун. 1 и 3 приходят во время кулдауна, поэтому они полностью отбрасываются. Кулдаун заканчивается до прибытия 4 , поэтому он передается.

какая-то диаграмма

Я бы хотел, чтобы это произошло:

 1 3 4 

После получения 1 , дроссель должен продолжать кулдаун в течение 1 секунды. Затем он должен получить 2 и записать их позже, потому что он все еще находится в режиме восстановления. Затем он получает 3 , которые заменяют 2 поданные на потом. Затем дроссель отключается, и в этот момент он должен немедленно отправить 3 . Наконец, 4 прибывает, когда этот раунд возвращается во время перезарядки, поэтому он отправляется немедленно.

диаграмма «что-я-пожелание»

Если у zsh были замыкания , я бы запустил подоболочку, которая спит для $INTERVAL , затем echo последнюю полученную LINE , но, увы, zsh не имеет замыканий.

4 Solutions collect form web for “Как отключить, как часто передаются строки вывода?”

Проблема в том, что вам нужно прочитать с таймаутом . Если firehose ничего не отправляет, ваш цикл блокируется бесконечно , и когда он это делает, он не может отправить строку, которая была получена в последнее время. У Bash есть аргумент -t для пронумерованного чтения. Если read zsh, это будет полезно.

Алгоритм состоит в том, чтобы сохранить строки чтения с тайм-аутом, который всегда пересчитывается (сокращается все больше и больше), чтобы истечь в конце одной секунды (или любого другого) интервала. Когда этот интервал прибывает, тогда, если одна или несколько строк были прочитаны, отправьте последний. В противном случае ничего не посылайте и теперь начинайте чтение строк для следующего интервала.

Вы можете реализовать «мгновенный проход» для самой первой полученной строки или для первой строки, которая получена через период времени, превышающий интервал. Например, если интервал составляет 1 секунду, и ничего не произошло из firehose за 1,5 с момента последнего вывода строки, то эта строка может быть пропущена, и машина может сбрасывать, чтобы начать новый интервал в одну секунду в этой точке.

Эта реализация доказательной концепции в TXR Lisp работает для меня, проверяя базовый алгоритм:

 (defvarl %interval% 1000000) ;; us (defun epoch-usec () (tree-bind (sec . usec) (time-usec) (+ (* 1000000 sec) usec))) (let ((now (epoch-usec)) (*stdin* (open-fileno (fileno *stdin*) "rl")) ;; line buffered remaining-time next-time line done) (while (not done) (set next-time (+ now %interval%)) (set remaining-time (- next-time now)) (while (poll (list (cons *stdin* poll-in)) (trunc remaining-time 1000)) ;; got a line or EOF poll: no timeout (iflet ((nline (get-line))) (set line nline) ;; got line (progn (flip done) (return))) ;; EOF poll (set now (epoch-usec)) (when (minusp (set remaining-time (- next-time now))) (return))) ;; timeout, past deadline or exit: flush line, if any: (when line (put-line line) (set line nil)))) 

Создается небуферизованный поток, потому что poll используется для тайм-аутов чтения, а в poll не отображаются потоковые буферы. Идея состоит в том, что мы не хотим быть опросом для ввода, в то время как в потоке есть непрочитанные буферизованные данные. Это ничто. В тестировании я действительно не видел каких-либо качественных различий в поведении между этим и просто используя буферизованный исходный поток *stdin* . Если мы тратим время на опрос, когда в потоке есть буферизованные данные, а в файловом дескрипторе нет, мы гарантируем, что не будем ждать дольше нашего интервала времени и меньше, если новые данные поступят раньше.

Мы предполагаем, что успешный poll означает, что мы можем прочитать полную строку. poll не гарантирует, что, конечно, но источники входного потока с хорошим исходным текстом должны обеспечить гарантию того, что если один байт ввода доступен для пробуждения, на этом байте имеется полная строка без какой-либо неоправданной задержки.

В остальных расчетах времени используется календарное время, тогда как в poll используется относительное ожидание, которое, вероятно, нечувствительно к настройкам времени. Таким образом, применяются обычные оговорки. Если часы внезапно отскакивают назад, упс!

Эти тестовые примеры проходят без какой-либо заметной задержки:

 $ echo foo | txr throttle.txr foo $ (echo foo; echo bar) | txr throttle.tl bar $ (echo foo; echo bar; echo xyzzy) | txr throttle.tl xyzzy 

Затем:

 $ (echo foo; sleep 2; echo bar; sleep 2; echo xyzzy) | txr throttle.tl foo bar xyzzy 

Я тестировал с помощью find / | txr throttle.tl find / | txr throttle.tl и т. д.

Первый вариант (не работает, см. Второй вариант)

Кажется, мы не можем использовать команду read для таких задач, потому что read останавливается while выполнении цикла.

Посмотрите на этот пример: (printf "1\n2\n3\n" ; sleep 5; printf "4\n") | while read -r line; do echo hello; done (printf "1\n2\n3\n" ; sleep 5; printf "4\n") | while read -r line; do echo hello; done (printf "1\n2\n3\n" ; sleep 5; printf "4\n") | while read -r line; do echo hello; done .

while цикл с read внутри будет выполнен таким образом:

  • 1 итерация – чтение 1 ;
  • 2 итерация – читать 2 ;
  • 3 итерация – читать 3 ;
  • 4 Итерация – ОЖИДАНИЕ 5 сек, затем прочитайте 4 .

Мы не можем делать запланированные работы внутри этого цикла, например «делать это каждые 1 секунду», потому что он будет периодически останавливаться, ожидая ввода. Например, он может ожидать, например, 1 минуту или больше, и наша запланированная работа также будет остановлена.

 function interval () { amount_of_seconds=$1 print_time=0 buffer='' while read -r line; do current_time=$(date +%s) if (( current_time > print_time )); then echo -e "${buffer}${line}" buffer='' print_time=$((current_time + amount_of_seconds)) else buffer="$line\n" fi done echo -en "$buffer" } 

Тестирование:

 $ alias firehose='(printf "1\n2\n3\n" ; sleep 2 ; printf "4\n"; sleep 2 ; printf "5\n6\n7\n" ; sleep 2; printf "8\n")' $ firehose | interval 1 | cat 1 3 4 5 7 8 $ 

Второй вариант

Перенаправить выход firehose в файл: firehose >> buffer_file.txt (Объяснение, почему >> и не > см. Ниже)

expensive-command будет считывать последнюю строку из этого файла каждую секунду и очищать файл:

 while true; do tail -n 1 buffer_file.txt | expensive-command # clear file echo -n '' > buffer_file.txt # and sleep 1 second sleep 1 done 

В результате у нас будет следующее:

  1. обе команды работают одновременно ( firehose в фоновом режиме):

    firehose >> buffer_file.txt & ./script_with_expensive_command_inside.sh

    Оператор APPEND – >> нужен после firehose , а не WRITE > . В противном случае файл не будет очищен и будет расти непрерывно. Здесь объясняется это поведение.

  2. Все нежелательные линии будут отброшены, только последняя будет передана expensive command
  3. Последняя строка будет сохранена, прежде чем expensive command не прочитает ее и не очистит файл.

Я сделал это!

Вот мой interval скрипта (также на github ):

 #!/usr/bin/env zsh # Lets a line pass only once every $1 seconds. If multiple lines arrive during # the cooldown interval, only the latest is passed on when the cooldown ends. INTERVAL="$1" CHILD_PID= BUFFER=$(mktemp) CAN_PRINT_IMMEDIATELY=1 CAN_START_SUBPROCESS=1 # Reset state when child process returns child-return () { CAN_START_SUBPROCESS=1 CAN_PRINT_IMMEDIATELY=1 } trap child-return CHLD # Clean up when quitting cleanup () { kill -TERM "$CHILD_PID" &> /dev/null rm "$BUFFER" exit } trap cleanup TERM INT QUIT while read LINE; do # If we're just starting, just print immediately if [[ -n $CAN_PRINT_IMMEDIATELY ]]; then echo $LINE CAN_PRINT_IMMEDIATELY= else # Otherwise, store the line for later echo "$LINE" > $BUFFER # And spawn a subprocess to handle it one interval later, unless one is # already running. With the SIGCHLD trap, the state variables will # reset when it exits. if [[ -n $CAN_START_SUBPROCESS ]]; then CAN_START_SUBPROCESS= ( sleep $INTERVAL tail -n1 $BUFFER ) & CHILD_PID=$! fi fi done # Once we exhaust stdin, wait for the last child process to finish, if any. if [[ -n $CHILD_PID ]]; then wait $CHILD_PID &> /dev/null cleanup fi 

Я заметил, что строки read цикла не всегда могут отвечать за их печать, потому что программе иногда приходится печатать строки асинхронно (когда никто не получает, иногда даже долго после окончания stdin ). Следовательно, детский процесс.

Вот он работает, а вход также tee >(sed) 'd в сторону, чтобы наблюдать за временем:

Запись GIF, демонстрирующая, что сценарий работает на примере ввода, заданного в вопросе

Это соответствует моей предыдущей диаграмме:

диаграмму желаемого результата из вопроса

Это должно делать то, что вы хотите очень гладко 🙂

 firehose | awk '{print $1; system("sleep 1")}' | expensive-command 

У этого есть недостаток, что все это становится довольно сложно убить ( killall awk работает, но умеренно изящно), но по крайней мере это просто и не требует специального скрипта или чего-то еще.

  • zsh: как получить расширение файла, работающее в заявлении на печать
  • Выход функции zsh удаляет ведущие пробелы
  • Как использовать `which` в команде aliased?
  • Таинственное повторение введенной команды в оболочке
  • Приоритет && vs & in bash и zsh
  • tcsh vs zsh. Символы Escape / Control
  • Как «отменить» rsync -L?
  • Подсказка: замените пользовательский путь коротким словом, например ~ для дома
  • Как скопировать / выставить многострочное выражение, которое я ввел в приглашении ZSH?
  • Существует ли (предпочтительно не-kludgey) способ избежать орфографических исправлений при использовании команды zv `mv`
  • / usr / bin / env: zsh -: нет такого файла или каталога
  • Interesting Posts

    очень высокая память буферной памяти

    Нужно ли настраивать postfix для использования gmail в Google Apps?

    Присоединиться: два файла – но только добавить последние два столбца

    Присоединить несколько файлов в столбце каталога wise

    Простой скрипт Bash; Только работает?

    Как заставить rsync перезаписывать архивные файлы только для чтения?

    Как выровнять текст в середине двух $ {hr} s?

    Сломанные файлы после перемещения их назад и вперед

    Как удалить строку из файла, когда строка содержит определенную строку?

    Что вы используете, когда не можете использовать Bash?

    Изменение связанной библиотеки для данного исполняемого файла (CentOs 6)

    Каково значение точки в командах bash и чем она отличается от звездочки?

    Как я могу удалить символы <93> <94> в файле? Это эквивалентно ""

    Как инициализировать глобальный, ассоциативный массив только для чтения в Bash?

    Отправить javascript для Chromium или Firefox из linux terminal

    Linux и Unix - лучшая ОС в мире.