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

желание

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

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
  • Первые символы команды, повторяющиеся на дисплее при завершении
  • Как запустить указанный код с помощью getopts, когда параметры или аргументы не заданы?
  • Ограничение опции grep --color для интерактивной оболочки
  • Как я могу вернуться в «обычный» режим редактирования после нажатия esc в режиме zsh (vi)?
  • Завершения работы перестали работать после обновления zsh
  • Игнорировать файлы для завершения zsh для SVN
  • Как убить кучу заданий на основе вывода ps?
  • find -exec on zsh: эхо работает, как и ожидалось, печать не
  • iterm-синхронизация сеансов по новым вкладкам
  • Tmux-logging: zle reset-prompt причина войти в файл журнала
  • Interesting Posts
    Linux и Unix - лучшая ОС в мире.