How to retry a bash command until its status is ok or until a timeout is reached?
My best shot (I'm looking for something simpler):
NEXT_WAIT_TIME=0
COMMAND_STATUS=1
until [ $COMMAND_STATUS -eq 0 || $NEXT_WAIT_TIME -eq 4 ]; do
command
COMMAND_STATUS=$?
sleep $NEXT_WAIT_TIME
let NEXT_WAIT_TIME=NEXT_WAIT_TIME+1
done
You can simplify things a bit by putting command right in the test and doing increments a bit differently. Otherwise the script looks fine:
NEXT_WAIT_TIME=0
until [ $NEXT_WAIT_TIME -eq 5 ] || command; do
sleep $(( NEXT_WAIT_TIME++ ))
done
[ $NEXT_WAIT_TIME -lt 5 ]
One line and shortest, and maybe the best approach:
timeout 12h bash -c 'until ssh root#mynewvm; do sleep 10; done'
Credited by http://jeromebelleman.gitlab.io/posts/devops/until/
retry fuction is from:
http://fahdshariff.blogspot.com/2014/02/retrying-commands-in-shell-scripts.html
#!/bin/bash
# Retries a command on failure.
# $1 - the max number of attempts
# $2... - the command to run
retry() {
local -r -i max_attempts="$1"; shift
local -r cmd="$#"
local -i attempt_num=1
until $cmd
do
if (( attempt_num == max_attempts ))
then
echo "Attempt $attempt_num failed and there are no more attempts left!"
return 1
else
echo "Attempt $attempt_num failed! Trying again in $attempt_num seconds..."
sleep $(( attempt_num++ ))
fi
done
}
# example usage:
retry 5 ls -ltr foo
if you want to retry an function in your script, you should do like this:
# example usage:
foo()
{
#whatever you want do.
}
declare -fxr foo
retry 3 timeout 60 bash -ce 'foo'
Put together some tools.
retry: https://github.com/kadwanev/retry
timeout: http://manpages.courier-mta.org/htmlman1/timeout.1.html
Then see the magic
retry timeout 3 ping google.com
PING google.com (173.194.123.97): 56 data bytes
64 bytes from 173.194.123.97: icmp_seq=0 ttl=55 time=13.982 ms
64 bytes from 173.194.123.97: icmp_seq=1 ttl=55 time=44.857 ms
64 bytes from 173.194.123.97: icmp_seq=2 ttl=55 time=64.187 ms
Before retry #1: sleeping 0.3 seconds
PING google.com (173.194.123.103): 56 data bytes
64 bytes from 173.194.123.103: icmp_seq=0 ttl=55 time=56.549 ms
64 bytes from 173.194.123.103: icmp_seq=1 ttl=55 time=60.220 ms
64 bytes from 173.194.123.103: icmp_seq=2 ttl=55 time=8.872 ms
Before retry #2: sleeping 0.6 seconds
PING google.com (173.194.123.103): 56 data bytes
64 bytes from 173.194.123.103: icmp_seq=0 ttl=55 time=25.819 ms
64 bytes from 173.194.123.103: icmp_seq=1 ttl=55 time=16.382 ms
64 bytes from 173.194.123.103: icmp_seq=2 ttl=55 time=3.224 ms
Before retry #3: sleeping 1.2 seconds
PING google.com (173.194.123.103): 56 data bytes
64 bytes from 173.194.123.103: icmp_seq=0 ttl=55 time=58.438 ms
64 bytes from 173.194.123.103: icmp_seq=1 ttl=55 time=94.828 ms
64 bytes from 173.194.123.103: icmp_seq=2 ttl=55 time=61.075 ms
Before retry #4: sleeping 2.4 seconds
PING google.com (173.194.123.103): 56 data bytes
64 bytes from 173.194.123.103: icmp_seq=0 ttl=55 time=43.361 ms
64 bytes from 173.194.123.103: icmp_seq=1 ttl=55 time=32.171 ms
...
Check exit status for ultimate pass/fail.
For anyone wanting to actually wait until some time passed, taking into account the time of your command might be significant:
TIMEOUT_SEC=180
start_time="$(date -u +%s)"
while [ condition_or_just_true ]; do
current_time="$(date -u +%s)"
elapsed_seconds=$(($current_time-$start_time))
if [ $elapsed_seconds -gt $TIMEOUT_SEC ]; then
echo "timeout of $TIMEOUT_SEC sec"
exit 1
fi
echo "another attempt (elapsed $elapsed_seconds sec)"
some_command_and_maybe_sleep
done
I made some tweaks to this answer which let you switch on whether the timeout was reached, or whether the command succeed. Also, in this version there is a retry every second:
ELAPSED=0
started=$(mktemp)
echo "False" > $started
until the_command_here && echo "True" > $started || [ $ELAPSED -eq 30 ]
do
sleep 1
(( ELAPSED++ ))
done
if [[ $(cat $started) == "True" ]]
then
echo "the command completed after $ELAPSED seconds"
else
echo "timed out after $ELAPSED seconds"
exit 111
fi
Related
This question already has answers here:
How to compare strings in Bash
(12 answers)
Closed 2 years ago.
Ok I need help figuring out why this code doesn't work, I've listed my problem below the code.
#!/bin/bash
if [ "$1" == "" ]
then
echo "You forgot an IP adress!"
echo "Syntax: ./ipsweep.sh xxx.xxx.x"
else
for ip in `seq 1 254`; do
ping -c 1 $1.$ip | grep "64 bytes" | cut -d " " -f 4 | tr -d ":" &
done
fi
now when I run the command ./ipsweeper.sh the program still runs even though the input is nothing. Please help can't see where it fails.
This:
if [ "$1" == "" ]
should be changed to:
if [ -z "$1" ]
-z is true if the string is zero length.
== is used with [[ ]] while = is used with [ ].
You can read more about bash string comparison in How to Compare Strings in Bash.
You can also check for...
pingit(){
ping -c1 ${1}
}
if [ ${#} -gt 0 ]
then
pingit ${1}
fi
...the number of arguments. Then you can source it without argument or use it with argument...
# . pingit.sh
# pingit localhost
PING localhost(localhost (::1)) 56 data bytes
64 bytes from localhost (::1): icmp_seq=1 ttl=64 time=0.058 ms
--- localhost ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.058/0.058/0.058/0.000 ms
# sh pingit.sh localhost
PING localhost(localhost (::1)) 56 data bytes
64 bytes from localhost (::1): icmp_seq=1 ttl=64 time=0.031 ms
--- localhost ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.031/0.031/0.031/0.000 ms
The goal of code is that, I want to make a random tcp traffic using iperf and capture it over 5, 10, 15, 20 seconds using tcpdump. In addition, capturing the throughput is also important for me. My problem is that, I would like to execute code1, code2, code3 and code4 for 5, 10, 15 and 20 seconds in bash. However I don't know how to put the mentioned condition for it. Here is my code:
for Test_duration in 5 10 15 20
do
echo “Test performing with $Test_duration duration”
sudo tcpdump -G 10 -W 2 -w /tmp/scripttest_$Test_duration -i h1-eth0 &
while true; do
#code1
time2=$(($RANDOM%20+1))&
pksize2=$(($RANDOM%1000+200))&
iperf -c 10.0.0.2 -t $time2 -r -l $pksize2 >> /media/sf_sharedsaeed/throughtput/iperthroughput_host2_$Test_duration.txt &\
#code2
time3=$(($RANDOM%20+1))&
pksize3=$(($RANDOM%1000+200))&
iperf -c 10.0.0.3 -t $time3 -r -l $pksize3 >> /media/sf_sharedsaeed/throughtput/iperthroughput_host3_$Test_duration.txt &\
#code3
time4=$(($RANDOM%20+1))&
pksize4=$(($RANDOM%1000+200))&
iperf -c 10.0.0.4 -t $time4 -r -l $pksize4 >> /media/sf_sharedsaeed/throughtput/iperthroughput_host4_$Test_duration.txt &\
#code4
time5=$(($RANDOM%20+1))&
pksize5=$(($RANDOM%1000+200))&
iperf -c 10.0.0.5 -t $time5 -r -l $pksize5 >> /media/sf_sharedsaeed/throughtput/iperthroughput_host5_$Test_duration.txt &\
done
done
Another constraint is that, code1, code2, code3 and code4 should be executed at the same time so, I used &.
Please help me what should I replace instead of while true; to have periodic execution of codes. Can any body help me?
You could do that by using a background subshell that creates a simple file lock on expiration that you detect from your while loops. Here is an example based on a simplified version of your code:
for Test_duration in 5 10 15 20
do
# TIMEOUT_LOCK will be your file lock
rm -f TIMEOUT_LOCK
# next command will run in a parallel subshell at the background
(sleep $Test_duration; touch TIMEOUT_LOCK) &
echo “Test performing with $Test_duration duration”
while true; do
# check whether current timeout (5 or 10 or 15 ...) has occured
if [ -f TIMEOUT_LOCK ]; then rm -f TIMEOUT_LOCK; break; fi
# do your stuff here - I'm just outputing dots and sleeping
echo -n "."
sleep 1
done
echo ""
done
The output of this code is:
“Test performing with 5 duration”
.....
“Test performing with 10 duration”
..........
“Test performing with 15 duration”
...............
“Test performing with 20 duration”
....................
host.txt:
www.google.com
test.sh:
#!/usr/bin/env bash
while IFS=$'\n' read -r line; do
echo $line
echo "#1"
ping -c 1 $line
line2="www.google.com"
echo "#2"
ping -c 1 $line2
done < $hostfile
exit 0
output:
> test.sh
www.google.com
#1
ping: unknown host www.google.com
#2
PING www.google.com (74.125.206.147) 56(84) bytes of data.
64 bytes from wk-in-f147.1e100.net (74.125.206.147): icmp_seq=1 ttl=46 time=22.1 ms
--- www.google.com ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 22.111/22.111/22.111/0.000 ms
Could someone tell why first ping failed?
Thanks.
Possibly your txt file contains CRLF line endings
and bash actually tried to ping www.google.com^M name.
I am reading from the shared memory a stream producing an infinite information output such as:
0x1 (TimeStamp) 12Bytes:11216 + 1771/(47999+1) (0.036896) delta= 0+ 1536/(47999+1) (0.032000) 11216.013361 23.534ms 2015.06.25 11:51:16.525
0x4 (ReferenceTime) 12Bytes:11215 + 24786286/(26999999+1) (0.918011) delta= 0+ 806359/(26999999+1) (0.029856) 11216.013376 -95.366ms 2015.06.25 11:51:16.525
0x6 (ProcessDelay) 4Bytes: 32 (0x20)
0x7 (ClockAccuracy) 8Bytes: offset=0.000ppm (+-0.000ppm)
0xb (ClockId) 8Bytes: 01 00 00 00 42 22 01 00
0x20001 (SampleRate) 4Bytes: 48000 (0xbb80)
0x20002 (Channels) 4Bytes: 6 (0x6)
0x20003 (PcmLevel) 24Bytes: -11041 -11541 -49076 -86121 -24846 -24382
0x20004 (PcmPeak) 24Bytes: -8088 -8697 -37244 -84288 -21437 -21769
0x2000e (DolbyDpMetadata) 39352Bytes:
Linear Time: 11216 + 1771/(47999+1) (0.036896) delta= 0+ 1536/(47999+1) (0.032000)
if i try to read the stream with the following command:
while read line;
do
echo "$line";
echo "im here!"
done < <(../tools/spucat adec-68)
wherespucat is a cpp binary exacutable that continuosly print out on console using printf() information about incoming data packets.
this is the result:
im here!
�k�G��E�x����b��h�������c����2��/n��-�U���QE�L�x���c�������������������������������x��4����O��M�����/��(������������������~��E�*�������;
im here!
������r��$�|��J�n�P�4�
if i start the script whit this command:
while read line;
do
echo "$line";
echo "im here!"
done < $(../tools/spucat adec-68)
it actually never go inside the while loop, just start to print out the stream whaiting for the end.
Is tehere a way to read it line by line and process it inside the while loop?
spucat is dumping to the standard error (no idea why), so to processing it must be redirect to the standard output:
while read -r line;
do
echo "$line";
echo "im here!"
done < <(../tools/spucat -p 4 adec-68 2>&1 > /dev/null)
In command below I enable file /dev/tcp/10.10.10.1/80 both for reading and writing and associate it with file descriptor 3:
$ time exec 3<>/dev/tcp/10.10.10.1/80
bash: connect: Operation timed out
bash: /dev/tcp/10.10.10.1/80: Operation timed out
real 1m15.151s
user 0m0.000s
sys 0m0.000s
This automatically tries to perform TCP three-way handshake. If 10.10.10.1 is not reachable as in example above, then connect system call tries to connect for 75 seconds. Is this 75 second timeout determined by bash? Or is this system default? Last but not least, is there a way to decrease this timeout value?
It's not possible in Bash without modifying the source as already mentioned, although here is the workaround by using timeout command, e.g.:
$ timeout 1 bash -c "</dev/tcp/stackoverflow.com/80" && echo Port open. || echo Port closed.
Port open.
$ timeout 1 bash -c "</dev/tcp/stackoverflow.com/81" && echo Port open. || echo Port closed.
Port closed.
Using this syntax, the timeout command will kill the process after the given time.
See: timeout --help for more options.
It is determined by TCP. It can be decreased on a per-socket basis by application code.
NB The timeout only takes effect if there is no response at all. If there is a connection refusal, the error occurs immediately.
No: there is no way of changing timeout by using /dev/tcp/
Yes, you could change default timeout for TCP connection in any programming language.
But, bash is not a programming language!
You could have a look into source code (see: Bash Homepage), you may find lib/sh/netopen.c file where you could read in _netopen4 function:
s = socket(AF_INET, (typ == 't') ? SOCK_STREAM : SOCK_DGRAM, 0);
You could read this file carefully, there are no consideration of connection timeout.
Without patching bash sources, there is no way of changing connection timeout by a bash script.
Simple HTTP client using netcat (near pure bash)
There is a little sample HTTP client written in pure bash, but using netcat:
#!/bin/bash
tmpfile=$(mktemp -p $HOME .netbash-XXXXXX)
exec 7> >(nc -w 3 -q 0 stackoverflow.com 80 >$tmpfile)
exec 6<$tmpfile
rm $tmpfile
printf >&7 "GET %s HTTP/1.0\r\nHost: stackoverflow.com\r\n\r\n" \
/questions/24317341/how-to-decrease-tcp-connect-system-call-timeout
timeout=100;
while ! read -t .001 -u 6 status ; do read -t .001 foo;done
echo STATUS: $status
[ "$status" ] && [ -z "${status//HTTP*200 OK*}" ] || exit 1
echo HEADER:
while read -u 6 -a head && [ "${head//$'\r'}" ]; do
printf "%-20s : %s\n" ${head%:} "${head[*]:1}"
done
echo TITLE:
sed '/<title>/s/<[^>]*>//gp;d' <&6
exec 7>&-
exec 6<&-
This could render:
STATUS: HTTP/1.1 200 OK
HEADER:
Cache-Control : private
Content-Type : text/html; charset=utf-8
X-Frame-Options : SAMEORIGIN
X-Request-Guid : 46d55dc9-f7fe-425f-a560-fc49d885a5e5
Content-Length : 91642
Accept-Ranges : bytes
Date : Wed, 19 Oct 2016 13:24:35 GMT
Via : 1.1 varnish
Age : 0
Connection : close
X-Served-By : cache-fra1243-FRA
X-Cache : MISS
X-Cache-Hits : 0
X-Timer : S1476883475.343528,VS0,VE100
X-DNS-Prefetch-Control : off
Set-Cookie : prov=ff1129e3-7de5-9375-58ee-5f739eb73449; domain=.stackoverflow.com; expires=Fri, 01-Jan-2055 00:00:00 GMT; path=/; HttpOnly
TITLE:
bash - How to decrease TCP connect() system call timeout? - Stack Overflow
Some explanations:
We create first a temporary file (under private directory for security reason), bind and delete before using them.
$ tmpfile=$(mktemp -p $HOME .netbash-XXXXXX)
$ exec 7> >(nc -w 3 -q 0 stackoverflow.com 80 >$tmpfile)
$ exec 6<$tmpfile
$ rm $tmpfile
$ ls $tmpfile
ls: cannot access /home/user/.netbash-rKvpZW: No such file or directory
$ ls -l /proc/self/fd
lrwx------ 1 user user 64 Oct 19 15:20 0 -> /dev/pts/1
lrwx------ 1 user user 64 Oct 19 15:20 1 -> /dev/pts/1
lrwx------ 1 user user 64 Oct 19 15:20 2 -> /dev/pts/1
lr-x------ 1 user user 64 Oct 19 15:20 3 -> /proc/30237/fd
lr-x------ 1 user user 64 Oct 19 15:20 6 -> /home/user/.netbash-rKvpZW (deleted)
l-wx------ 1 user user 64 Oct 19 15:20 7 -> pipe:[2097453]
$ echo GET / HTTP/1.0$'\r\n\r' >&7
$ read -u 6 foo
$ echo $foo
HTTP/1.1 500 Domain Not Found
$ exec 7>&-
$ exec 6>&-