희망
파이프라인에 응답하여 명령을 반복적으로 실행하고 싶습니다.
firehose | expensive-command
그러나 나는 많은 줄을 받았고 명령은 매우 리소스 집약적입니다. 최대 한 번만 실행되도록 명령에 대한 입력을 필터링하고 싶습니다.엑스두번째:
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에는 클로저가 없습니다.
답변1
문제는읽기에 시간 제한이 필요합니다.. 아무것도 전송되지 않으면 firehose
루프무기한 차단, 그리고 이렇게 하면 가장 최근에 받은 행을 보내는 데 실패합니다.Bash에는 시간 제한 읽기를 나타내는 -t 매개변수가 있습니다.zsh에 read
이것이 있으면 사용할 수 있습니다.
알고리즘은 지속적으로 행을 읽고 1초(또는 기타) 간격이 끝나면 만료되도록 항상 다시 계산되는(점점 더 짧아짐) 제한 시간을 설정하는 것입니다. 이 간격에 도달하면 하나 이상의 행을 읽었으면 마지막 행이 전송됩니다. 그렇지 않으면 아무 것도 전송되지 않으며 이제 다음 행 간격을 읽기 시작합니다.
수신된 첫 번째 라인 또는 간격 시간보다 긴 후 수신된 첫 번째 라인에 대해 "즉시 전달"을 구현할 수 있습니다. 간격이 1초이고 firehose
마지막 라인이 출력된 이후 1.5초 동안 아무것도 없으면 라인이 통과할 수 있으며 기계는 해당 지점에서 새로운 1초 간격을 시작하도록 재설정될 수 있습니다.
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
스트림 버퍼가 표시되지 않으므로 버퍼링되지 않은 스트림을 설정하십시오 . 스트림에 읽지 않은 버퍼링된 데이터가 있을 때 입력을 폴링하고 싶지 않다는 아이디어입니다. 이것은 nitpick입니다. 테스트에서는 *stdin*
이 동작과 버퍼링된 원시 스트림을 사용하는 것 사이의 동작에서 질적인 차이를 실제로 보지 못했습니다 . 스트림에 버퍼링된 데이터가 있지만 파일 설명자에 데이터가 없을 때 폴링 시간을 낭비하는 경우 간격보다 더 오래 기다리지 않고 새 데이터가 더 일찍 도착하면 대기 시간이 간격보다 작아집니다.
우리는 성공이 poll
전체 줄을 읽을 수 있다는 것을 의미한다고 가정합니다. poll
물론 이를 보장할 수는 없지만 제대로 작동하는 텍스트 스트림 입력 소스는 입력 바이트가 wakeup에 사용 가능한 경우 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
등등을 테스트해봤습니다 .
답변2
첫 번째 변형(작동하지 않음, 두 번째 변형 참조)
루프 실행이 중지되므로 read
명령을 사용하여 이러한 작업을 수행 할 수 없는 것 같습니다 .read
while
이 예를 보세요: (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
결과적으로 우리는 다음을 얻게 될 것입니다:
- 두 명령이 동시에 실행됩니다(
firehose
백그라운드에서):
firehose >> buffer_file.txt & ./script_with_expensive_command_inside.sh
APPEND 연산자 - WRITE>>
이후에 필요하며 필요 하지 않습니다 . 그렇지 않으면 파일이 정리되지 않고 계속 커집니다.firehose
>
다음은 이 동작에 대한 설명입니다. - 불필요한 행은 모두 삭제되고 마지막 행만 전달됩니다.
expensive command
expensive command
마지막 줄은 읽지 않고 파일을 지우기 전에 저장됩니다 .
답변3
내가 해냈어!
여기 내 interval
스크립트가 있습니다(또한깃허브에서):
#!/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)
이는 다음과 같이 작동하며 시간을 관찰하기 위해 입력도 따로 보관됩니다 .
이는 이전 다이어그램과 일치합니다.
답변4
이것은 매우 간단한 방법으로 원하는 것을 수행해야 합니다 :)
firehose | awk '{print $1; system("sleep 1")}' | expensive-command
단점은 모든 것을 죽이기가 약간 어렵다는 것입니다( killall awk
작동하지만 매우 우아함). 그러나 최소한 간단하고 특별한 스크립트나 기타 항목이 필요하지 않습니다.