3

I have two conditions wait some time and check if within directory is more than one file. When one of these conditions is true loop has to be done so it is normal OR logical statement.

The expected behavior means that if time turn by or in directory is more than one file loop will be over. Currently is still waiting for more than one file. Can someone explain me why my while loop doesn't work as expected?

i=1; while [ $i -le 10 ] || [ $( ls /opt/hosts/ | wc -l ) -lt 2 ]; do sleep 3 $(( i++ )); done

Unfortunately i need one line command. I am wondering why my command is endless loop why OR does not work like should work: (i changed first condition to 3)

+ echo host_name 
+ i=1 
+ '[' 1 -le 2 ']' 
+ sleep 1 1 
+ '[' 2 -le 2 ']' 
+ sleep 1 2 
+ '[' 3 -le 2 ']' 
++ ls /opt/hosts/ 
++ wc -l 
+ '[' 1 -lt 2 ']' 
+ sleep 1 3 
+ '[' 4 -le 2 ']' 
++ ls /opt/hosts/ 
++ wc -l 
+ '[' 1 -lt 2 ']' 
+ sleep 1 4 
+ '[' 5 -le 2 ']' 
++ ls /opt/hosts/ 
++ wc -l 
+ '[' 1 -lt 2 ']' 
+ sleep 1 5 ```
3
  • 1
    What is "expected", what does it do? You are missing a ; after the 3. Sleep adds together all its args, so you are sleeping 3, 4, 5, 6 ... in successive loops. May 18 '20 at 9:01
  • I would prefer: for (( i = 1; i <= 10; i++ )); do .... It simplifies both the loop control and the test. May 18 '20 at 9:08
  • The expected behavior means that if time turn by or in directory is more than one file loop will be over. Currently is still waiting for more than one file. I do not understand why OR statement waiting for true from second condition - like in AND statement.
    – adamo89
    May 18 '20 at 9:21
9
  • You're giving sleep two arguments, 3 and $(( i++ )). With GNU tools, this means that the loop would sleep an increasing number of seconds in each iteration. On a non-GNU system, you would get an error from sleep.

  • Your loop will always run at least 10 times due to the way you have written your test. Your test says "Iterate while $i is less or equal to 10". Once $i reaches 10, the other part of the test will come into effect. You probably want a logical AND test here rather than an OR.

  • You should not use ls | wc -l to count files in a directory. This will give the wrong result in some (albeit pathological) cases.

Instead:

i=1
while [ "$i" -ne 10 ]; do
    set -- /opt/hosts/*
    if [ "$#" -ge 2 ]; then
        break
    fi

    sleep 3         # or, to sleep longer and longer: sleep "$(( 3 + i ))"
    i=$(( i + 1 ))
done

This properly increments i in each iteration, and also uses a safer way to count the number of names in the /opt/hosts directory (by expanding the * glob in the directory and counting the number of names that it expands to). The loop exits whenever the number of names is two or more, or whenever $i reaches the value 10.

After the loop, if $i is 10, then the files failed to materialize.

If you need to preserve your positional parameters (these would be overwritten by the set command), then expand the glob into an array with names=( /opt/hosts/* ) and then use "${#names[@]}" to get the length of that array instead of using "$#".

You could also write it as

i=1
while [ "$i" -ne 10 ] && ( set -- /opt/hosts/*; [ "$#" -lt 2 ] )
do
    sleep 3
    i=$(( i + 1 ))
done

This would not clobber your existing positional parameters as set is running in a subshell. It would also be closer to the type of thing you attempted yourself.


As a "one-liner":

i=1; while [ "$i" -ne 10 ]; do set -- /opt/hosts/*; [ "$#" -ge 2 ] && break; sleep 3; i=$(( i + 1 )); done

Or, with an "arithmetic for loop" in bash:

for (( i=1; i<=10; i++ )); do set -- /opt/hosts/*; [ "$#" -ge 2 ] && break; sleep 3; done

Or, with that last piece of code above the divider:

i=1; while [ "$i" -ne 10 ] && ( set -- /opt/hosts/*; [ "$#" -lt 2 ] ); do sleep 3; i=$(( i + 1 )); done
2
  • AFAIK, set -- has the unfortunate side-effect of unsetting all your own positional arguments in a script. May 18 '20 at 11:27
  • @Paul_Pedant Added a bit about that.
    – Kusalananda
    May 18 '20 at 11:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.