Wait until a condition becomes true in bash - bash

When writing shell scripts, I repeatedly have the need to wait for a given condition to become true, e.g. a remote URL becoming available (checked with curl) or a file that should exist, etc.
Ideally, I'd like to have a function or script await such that I can write, e.g.,
await [[ some condition ]]
and it would check the condition every second until it becomes true or a timeout occurs. Ideally I can set the polling interval and the timeout.
Is there a tool for this out there?

You can use an until loop:
until some condition
do
sleep 5
done
e.g.
until nc -z localhost 22
do
echo "SSHd is not up yet. Waiting..."
sleep 5
done
If you want to add a timeout, you'll have to add that separately with a counter or using the internal SECONDS variable:
SECONDS=0
until nc -z localhost 22
do
if (( SECONDS > 60 ))
then
echo "Giving up..."
exit 1
fi
echo "SSHd is not up yet. Waiting..."
sleep 5
done

I'd use the timeout utility around a while loop that uses sleep.
E.g.:
timeout 1 bash -c 'while :; do echo check; sleep 0.1; done '

Related

Bash wait terminates immediately?

I want to play a sound after a command finishes, but only if the command took more than a second.
I have this code (copied from https://stackoverflow.com/a/11056286/1757964 and modified slightly):
( $COMMAND ) & PID=$!
( sleep 1; wait -f $PID 2>/dev/null; paplay "$SOUND" ) 2>/dev/null & WATCH=$!
wait -f $PID 2>/dev/null && pkill -HUP -P $WATCH
The "wait" on the second line seems to terminate immediately, though. I tried the -f flag but the behavior didn't change. How can I make the "wait" actually wait?
The problem is that you're running wait in a subshell. A shell can only wait for its own children, but the process you're trying to wait for is a sibling of the subshell, not a child.
There's no need to use wait for this. The question you copied the code from is for killing a process if it takes more than N seconds, not for telling how long a command took.
You can use the SECONDS variable to tell if it took more than a second. This variable contains the number of seconds since the shell started, so just check if it has increased.
start=$SECONDS
$COMMAND
if (($SECONDS > $start))
then
paplay "$SOUND"
fi
I'd probably want to streamline this to capture the data (in # of seconds since epoch) and then compare the difference (and if > 1 second then play sound), eg:
prgstart=$(date '+%s') # grab current time in terms of # of seconds since epoch
$COMMAND # run command in foreground => no need for sleep/wait/etc; once it completes ...
prgend=$(date '+$s') # grab current time in terms of # of seconds since epoch
if [[ $(( ${prgend} - ${prgstart} )) -gt 1 ]]
then
paplay "$SOUND"
fi

Can I use timeout's command in bash for a block of code?

I would like to use timeout command, but It supports only to timeout a single command.
My goal is to do something like this - waiting untill a list of ports are up:
timeout 60 for port in $ports
do
while ! nc -zv localhost $port; do
sleep 1
done
done
if [[ $? -ne 0 ]]; then
echo not all ports up on time
fi
I want the for loop to stop if 60 seconds have passed, and check if it was success or not.
I understand that I can achieve this by using something like:
timeout 60 bash -c "..."
But this will be very unreadable. I thought maybe a bash function would work as a command but it didn't work..
Any ideas?
After some tests, I successfully found a way to implement this using bash functions, although it looks a bit weird, it is still more readable. Here is an example:
function my_loop() {
for port in $ports; do
while ! nc -zv localhost $port; do
sleep 1
done
done
}
export -f my_loop
timeout 60 bash -c "my_loop"
if [[ $? -ne 0 ]]; then
echo not all ports up on time
fi

I want to open a netcat connection and keep sending some text forever

echo " The NC send commands on port 2612"
while :
do
echo "hello " | nc -q -1 <some IP> 2612
done
I want open a netcat session forever, and that is been achived by -q -1.
How can I send "hello" on the same channed in every 20 second.
my earlier script was as following, but that opens nc connection every time.
What I really want is to open connection once and send "echo hello" evey 20 sec.
while :
do
echo "hello " | nc 192.168.100.161 2612
sleep 20
done
while echo "hello"; do
sleep 20
done | nc -q 192.168.100.161 2612
Note that we moved the echo into the condition of the loop, so that we stop trying to run it if nc exits (causing the echos to fail).
If you want to be able to retain state (for instance, a counter) from inside the loop and access it after the pipeline exited, then things need to change a bit further:
#!/bin/bash
# ^^^^- process substitutions aren't part of POSIX sh
count=0
while echo "hello"; do
(( ++count ))
sleep 20
done > >(nc -q 192.168.100.161 2612)
echo "Ran $count loops before exiting" >&2
There, the redirection is done as a process substitution, and the loop takes place inside the same shell instance that continues after it exits. See BashFAQ #24 for more details on this problem and solution.

Creating a shell script to check network connectivity

I'm making a simple shell script that runs an infinite loop, then if the output of the ping command contains "time" (indicating that it pinged successfully) it should echo "Connected!", sleep 1, and clear. However, I get no output from my script.
#!/bin/bash
while :
do
if [[ $(ping google.com) == *time* ]];
then
echo -en '\E[47;32m'"\033[1mS\033[0m"
echo "Connected!"
else
echo -en '\E[47;31m'"\033[1mZ\033[0m"
echo "Not Connected!"
fi
clear
sleep 1
done
Your script is not giving output because ping never terminates. To get ping to test your connectivity, you'll want to give it a run count (-c) and a response timeout (-W), and then check its return code:
#!/bin/bash
while true
do
if ping -c 1 -W 5 google.com 1>/dev/null 2>&1
then
echo "Connected!"
break
else
echo "Not Connected!"
sleep 1
fi
done
ping will return 0 if it is able to ping the given hostname successfully, and nonzero otherwise.
It's also worth noting that an iteration of this loop will run for a different period of time depending on whether ping succeeds quickly or fails, for example due to no network connection. You may want to keep the iterations to a constant length of time -- like 15 seconds -- using time and sleep.

bash. while loop with xargs kill -9

I have a list of IP addresses and I have to run a command for every single IP address.
I did it with this code:
array=($(</tmp/ip-addresses.txt))
for i in "${array[#]}"; do
./command start $i &
done
Now, the list of IP addresses is constantly refreshed every 2 minutes and I need to kill every command that is no longer with the new IP addresses.
Practically, the command needs to be executed again every 2 minutes with the refreshed IP addresses and all the another old IP needs to be killed.
How can I do that?
A simple workaround: (Not tested)
sleep_delay=120 # 2 mins
while true; do
(
array=($(</tmp/ip-addresses.txt))
for i in "${array[#]}"; do
./command start $i &
done
sleep $(( sleep_delay + 2 )) # 2 can be any number >0
) & PPID=$!
sleep $sleep_delay
pkill -9 -p $PPID
done
Note: I have not optimized your code, just added a wrapper around your code.
EDIT:
Edited code to satisfy requirement that the old processes should not be killed, if the IP is still same.
NOTE: I haven't tested the code myself, so be careful while using the kill command. You can test by putting echo before the kill statement. If it works well, you can use the script...
declare -A pid_array
while true; do
array=($(</tmp/ip-addresses.txt))
for i in `printf "%s\n" ${!pid_array[#]} | grep -v -f <(printf "%s\n" ${array[#]})`; do
kill -9 ${pid_array[$i]} # please try to use better signal to kill, than SIGKILL
unset pid_array[$i]
done
for i in "${array[#]}"; do
if [ -z "${pid_array[$i]}" ]; then
./command start $i & pid_array[$i]=$!
fi
done
sleep 120
done
Your script would be changed like:
#!/bin/bash
GAP=120
while :
do
#Do your stuff here
sleep $GAP
done
exit 0
After two minutes it would read from refreshed file

Resources