3

Here is a shell script. Please extrapolate the controlled-environment random number generation subshell to the use of this in an asynchronous logging situation (I intend to use it for inotifywait output).

group=0
(
  for val in {1..10}; do 
    echo "$RANDOM/20000" | bc | xargs sleep # this waits 0, 1, or 2 seconds before each number is printed
    echo $val 
  done 
) | while true; do 
      while read -t 1 line; do 
        echo "read time=$group read=$line" 
      done
      ((group++))
    done

This produces output like this:

read time=1 read=1
read time=2 read=2
read time=2 read=3
read time=3 read=4
read time=3 read=5
read time=3 read=6
read time=4 read=7
read time=5 read=8
read time=5 read=9
read time=5 read=10

But it then hangs and does not exit. It is stuck in the outer loop, continually incrementing group:

$ echo $group 
1336794

Clearly the inner loop has exited due to input having finished, and the incrementation of the variable went into hyperdrive.

How to exit the loop? Is there like a else clause of some sort that I can hack on to the inner for loop in order to break out of the outer loop, once the input has finished coming in?

Surely there must be a more robust method than doing timing in the outer loop to see if it went through too fast.

4
  • did you try break n .... n you can specify the nesting/number of loops you wanna get out of .... (if check for the break 2 stmt.) Commented Jun 6, 2013 at 4:09
  • Notice how i never wrote a break. Where would it go? The inner loop times itself out every second. Commented Jun 6, 2013 at 4:09
  • its a intresting question , +1 for that. Commented Jun 6, 2013 at 4:55
  • A controversial question, it seems Commented Jun 6, 2013 at 16:28

3 Answers 3

3
#!/bin/bash
mkfifo outputs
for val in {1..10}; do
    echo "$RANDOM/20000" | bc | xargs sleep
    echo $val 
done > outputs &
group=0
time=$(date +%s%N)
while read line; do
    ctime=$(date +%s%N)
    [ $(( $ctime - $time )) -gt 1000000000 ] && let group++
    echo "read time=$group read=$line"
    time=$ctime
done < outputs
Sign up to request clarification or add additional context in comments.

15 Comments

Where do i put it? The only thing I can think of is make the inner loop also infinite loop, and then manually break out of both loops when I notice the right condition from read.
Okay, that's reasonable to solve this meaningless example question, but in real life I don't know my log's gonna end right there, now am I?
according to manpage, read -t returns false when it times out. Doesn't it also return false when the input ends? Therefore i wouldnt be able to tell when input ends. As to the second point, I concede that, will edit
where does the input come from?
inotifywait -m. Trying to "collect" output so it wont run a script 50 times if I change 50 files simultaneously
|
1

Here is another solution based on read return code. According to bash documentation when read -t exit for timeout it has a return code greater than 128, so you can distinguish EOF from timeout.

function random_timed_output
{
  for val in {1..10}; do
    sleep $(( $RANDOM/20000 ))
    echo $val 
  done
}

group=0
random_timed_output | while true; do
  read -t 1 line
  rc=$?
  if [ $rc -gt 128 ]; then
    #read exit for timeout
    ((group++))
  elif [ $rc -gt 0 ]; then
    #read exit for error or EOF
    break
  else
    #read exit OK
    echo "read time=$group read=$line" 
  fi
done

3 Comments

Not on CentOS. Returns 1 both cases. Where'd you test this?
@StevenLu: Ubuntu 13.04, bash 4.2, see also documentation for `read' builtin here: gnu.org/software/bash/manual/bashref.html#Bash-Builtins
Well then. % bash --version GNU bash, version 3.2.25(1)-release (x86_64-redhat-linux-gnu) I think working around it with a manual timer makes for more portability. But definitely +1.
0
 Kaizen ~/so_test $ cat zcntr.sh
 ilimit=10; ## the length of list in for loop ... 

grp=0
(   for val in {1..10}; do
     #echo "$RANDOM/10000" | bc | xargs sleep
     sleep $(($RANDOM/10000));
     echo $val
     done
 ) | while true; do

  while read -t 1 line; do
    echo "read time=$grp read=$line"
  done

  ((grp++));

  if [ $grp -eq $ilimit ]
     then
       break ;
  fi

done

output is not satisfactory , but i will have a look at it later :

Kaizen ~/so_test $ ./zcntr.sh
  read time=0 read=1
  read time=0 read=2
  read time=1 read=
  read time=1 read=4
  read time=1 read=5
  ./zcntr.sh: line 13: read: read error: 0: Permission denied
  read time=4 read=7
  read time=6 read=
  read time=7 read=
  read time=7 read=10

does this help anyhow ?

5 Comments

@Steven Lu , okay ..... srry i didnt look at that , was trying to figure out why the read's failing ??
Maybe your $(($RANDOM/10000)) isn't as good as my original and writes zero bytes sometimes or does something else that is weird?
now i got even more confused ..... @ Steve could you provide a bit more info on what you are trying to do ? ... edit the question itself with the actual scenario if thats ok with you ?
yeah that is the case ..... i tested it seperately , it does give 0 sometimes ... but thats for sleep the var is set by the for counter which is echoed to the read in while via pipe..... whys that unassigned :(
I dunno. Read the other stuff here to see my motivations for this question. I believe @perreal solved it pretty nicely, I may post back with a non-fifo pipe only solution later if I can figure one out.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.