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 ```
Best Answer
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 fromsleep
.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:
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 withnames=( /opt/hosts/* )
and then use"${#names[@]}"
to get the length of that array instead of using"$#"
.You could also write it as
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":
Or, with an "arithmetic
for
loop" inbash
:Or, with that last piece of code above the divider: