Why this while condition don't break - bash

I am trying to wait my docker container is up to start some commands.
i am doing the following :
#!/bin/bash
DOCKER_IP=192.168.99.100
ES_PORT=9300
docker-compose up -d
while [ -z "$(nc -z $DOCKER_IP $ES_PORT)" ]; do
sleep 1
done
echo "Do some stuff now it's up !"
I already check with cat -e the return of nc -z is empty, without the quotes, with [ ! -n $(nc ...)] as conditional expression ...
When the return of nc is not empty, i have Connection to 192.168.99.100 port 9300 [tcp/vrace] succeeded! in loop and it never exit.
Why this simple loop condition is not working ?

The problem is most probably because nc prints the message "Connection to 192.168.99.100 port 9300 [tcp/vrace] succeeded!" on the standard error rather than standard output (otherwise you shouldn't see it). You can redirect standard error (so that it is captured by the command substitution) as follows:
while [ -z "$(nc -z $DOCKER_IP $ES_PORT 2>&1)" ]; do
sleep 1
done
However, not all versions of nc print such a message when connection succeeds (mine doesn't). So why don't you simply use the exit status of nc -z:
while ! nc -z $DOCKER_IP $ES_PORT
do
sleep 1
done

Related

bash - using a command line argument (hostname) to run an external command

First time post, please forgive any missing information.
I have a script that is supposed to work with icinga. I need icinga to log into my Linux box and run a command like "script ". The script will then run a command to that hostname like sudo /etc/init.d/apache2 status then report back "running or unused" and an exit status of 0 or 2.
I'm wondering how I could add another command and have it one or the other run depending on what hostname it's given. Half of them need apache2 to be running and the other half need to have a process called dss to be running. I'd rather not have two separate scripts. Here is the working script and sorry it's sloppy but I haven't done any clean up and I'm not real good at bash yet.
so the user would run the script ./chkdss2 or
#!/bin/bash
ec=0
ec1=2
var3=run
var4=unused
for host in "$#"
do
var1=`ssh $host sudo /etc/init.d/dss status|awk '{print $6}'`
var2="$( echo $var1 | cut -c 3-5 )"
if [[ "$var2" == "$var3" ]]; then
echo "$host is running"
echo $ec
else
echo "$host is not running"
echo $ec1
fi
done
There are a couple ways to test if a particular hostname is for apache or dss. You only need to have a list of hostnames for each case, and check if the received hostnames are included in said lists.
Method 1: using arrays
#!/bin/bash
# Method 1, using array lists of hosts
apachehosts=('ap1' 'ap2' 'ap3')
dsshosts=('dss1' 'dss2' 'dss3')
for host in "$#"
do
if printf '%s\n' "${apachehosts[#]}" | grep -Fxq "$host"
then
echo "$host: APACHE HOST"
elif printf '%s\n' "${dsshosts[#]}" | grep -Fxq "$host"
then
echo "$host: DSS HOST"
else
echo "ERROR, $host: unknown host"
fi
done
To modify the lists of hosts, simply add or remove values in the declaration of arrays apachehosts and dsshosts.
Method 2: using case
#!/bin/bash
# Method 2, using case
for host in "$#"
do
case "$host" in
'ap1'|'ap2'|'ap3')
echo "CASE, $host: APACHE HOST"
;;
'dss1'|'dss2'|'dss3')
echo "CASE, $host: DSS HOST"
;;
*)
echo "ERROR CASE, $host: unknown host"
;;
esac
done
Here, you edit the patterns in each case.
Method 3: using if
#!/bin/bash
# Method 3, using if
for host in "$#"
do
if [[ "$host" == 'ap1' || "$host" == 'ap2' || "$host" == 'ap3' ]]
then
echo "IF, $host: APACHE HOST"
elif [[ "$host" == 'dss1' || "$host" == 'dss2' || "$host" == 'dss3' ]]
then
echo "IF, $host: DSS HOST"
else
echo "IF, $host: unknown host"
fi
done
Here you modify the if conditions. I prefer the other methods, since this one is more complicated to edit, it is not as clear, especially if your list of hosts is long.
Method 4: condition on the hostnames
If you are lucky, there is some pattern to your hostnames. Ex. all apache servers start with letters ap, all your dss servers include dss in the name, ...
You can then simply use 2 if statements to decide which is which.
#!/bin/bash
# Method 4, patterns
for host in "$#"
do
if [[ $(echo "$host" | grep -c -e "^ap") -ne 0 ]]
then
echo "PATTERNS, $host: APACHE HOST"
elif [[ $(echo "$host" | grep -c -e "dss") -ne 0 ]]
then
echo "PATTERNS, $host: DSS host"
else
echo "PATTERNS, $host: unknown host"
fi
done
Note: hostname apdss1 would come out as an Apache server here. Previous methods would respond "unknown host". You patterns must be strict enough to avoid mismatches.
I had a similar task to get few report items using single ssh request.
I had to retrieve in singel ssh command:
Full hostname (FQDN)
Linux version
IP address of its Docker host if exist, or "none"
I got my script to work in 3 stage.
1. Get multiple lines of information from remote host
ssh -q dudi-HP-Compaq-Elite-8300-MT <<< '
date +%F:%T # line 1: time stamp
hostname -f # line 2: hostname
awk "/DESCR/{print \$3}" /etc/lsb-release # line 3 : host linux distribution version
ip a | awk "/inet / && !/127.0.0.1/{sub(\"/.*\",\"\",\$2);printf(\"%s \", \$2)}" # line 4: list IP address to the host
'
Results:
2022-03-05:22:22:21
dudi-HP-Compaq-Elite-8300-MT
20
192.168.2.111 192.168.122.1 172.17.0.1
2. Process multiple lines of information from remote host
Read lines of information from remote host, into an array sshResultsArr.
readarray -t sshResultsArr < <(ssh -q dudi-HP-Compaq-Elite-8300-MT <<< '
date +%F:%T # line 1: time stamp
hostname -f # line 2: hostname
awk "/DESCR/{print \$3}" /etc/lsb-release # line 3 : host linux distribution version
ip a | awk "/inet / && !/127.0.0.1/{sub(\"/.*\",\"\",\$2);printf(\"%s \", \$2)}" # line 4: list IP address to the host
')
hostname=${sshResultsArr[1]}
osVersion=${sshResultsArr[2]}
hasDockerIp=$(grep -Eo "172(.[[:digit:]]{1,3}){3}" <<< "${sshResultsArr[3]}") # find IP starting with 172
hasDockerIp=${hasDockerIp:="none"} # if not found IP set to "NONE"
printf "%s \t OS version: %s \t has Docker IP: %s\n" "$hostname" "$osVersion" "$hasDockerIp"
Result:
dudi-HP-Compaq-Elite-8300-MT OS version: 20 has Docker IP: 172.17.0.1
3. Process each remote host in a loop
#!/bin/bash
for host in "$#"; do
readarray -t sshResultsArr < <(ssh -q $host <<< '
date +%F:%T # line 1: time stamp
hostname -f # line 2: hostname
awk "/DESCR/{print \$3}" /etc/lsb-release # line 3 : host linux distribution version
ip a | awk "/inet / && !/127.0.0.1/{sub(\"/.*\",\"\",\$2);printf(\"%s \", \$2)}" # line 4: list IP address to the host
')
hostname=${sshResultsArr[1]}
osVersion=${sshResultsArr[2]}
hasDockerIp=$(grep -Eo "172(.[[:digit:]]{1,3}){3}" <<< "${sshResultsArr[3]}") # find IP starting with 172
hasDockerIp=${hasDockerIp:="none"} # if not found IP set to "NONE"
printf "%s \t OS version: %s \t has Docker IP: %s\n" "$hostname" "$osVersion" "$hasDockerIp"
done
I was able to take a little bit from the answers I received and put together something that works well. Thank you all for your answers.
for host in "$#"
do
case "$host" in
('vho1uc1-primary'|'vho1uc2-backup'|'vho2uc1-primary'|'vho2uc2-backup'|'vho3uc1-primary'|'vho3uc2-backup'|'vho10uc1-primary')
var1=`ssh "$host" sudo /etc/init.d/apache2 status|awk '{print $4}'`
var2="$( echo $var1 | cut -c 3-5 )"
if [[ "$var2" == "$var3" ]]; then
echo "Apache2 on $host is running"
echo "0"
else
echo "Apache2 on $host is not running"
echo "2"
fi
;;
*)
esac
done

shell script to find dynamic public ip address in ubuntu does not show any output

I wrote the following script to find my dynamic public IP address and save how often it is changes
#!/usr/bin/env bash
ip=0
change=0
for ((count = 10000; count != 0, change == 10; count--)); do
fetch="$(dig +short myip.opendns.com #resolver1.opendns.com)"
dig +short myip.opendns.com #resolver1.opendns.com >>/home/nik/Desktop/file.txt
if [ $ip == 0 ]; then
ip=fetch
elif [ $ip != "$fetch" ]; then
change++
echo $ip
echo " changed to "
echo "$fetch"
echo " at "
echo date
else
echo ""
fi
echo "123"
sleep 13
(( count--))
done
I saved file as script.sh and changed it's executable permissions using
chmod +x script.sh
When I independently run dig command(in next line) or echo command directly in terminal, they log output to file without any problem
dig +short myip.opendns.com #resolver1.opendns.com>>/home/nik/Desktop/file.txt
but when I run the script, It shows no output nor does it log anything into text file.
I use Ubuntu 19.10 if it matters.
Edit: added shebang and changed wait to sleep
You have change=0 in the beginning of your file, and then depend on change == 10 in the conditional expression of your for loop.
I think you should review your code first :-)
A good place to start with a script that tracks public IP Address changes might be this guy:
#!/usr/bin/env bash
CURRENT_IP="$(timeout 5 dig +short myip.opendns.com #resolver1.opendns.com 2>/dev/null)"
num_changes=0
while [ 1 ]
do
NEW_IP="$(timeout 5 dig +short myip.opendns.com #resolver1.opendns.com 2>/dev/null)"
if echo "${CURRENT_IP}" | grep -q "${NEW_IP}"
then
echo "IP is the same" > /dev/null
else
let num_changes++
echo "${num_changes}: ${CURRENT_IP} -> ${NEW_IP}"
CURRENT_IP="${NEW_IP}"
fi
done
There are two variables being used, CURRENT_IP and NEW_IP
They are both being updated the same way timeout 5 dig +short ... 2>/dev/null
The timeout serves to ensure our script never hangs forever
The 2>/dev/null serves to filter error messages away
The num_changes variable keeps track of the number of times the IP changed
The only time this script will ever print any message at all is when your address changes
Example output: [NUM_CHANGES]: [LAST ADDRESS] -> [NEW ADDRESS]
1: 75.72.13.89 -> 74.76.77.88
2: 75.72.13.88 -> 74.76.77.87
3: 75.72.13.87 -> 74.76.77.86

Saving a < /dev/null result from remote host to local machine in text file

I have the following code, which retrieves data from a remote host and display on local machine display.
if [ $(nc -z 192.168.80.180 22; echo $?) -eq 0 ]; then
ssh root#$192.168.80.180 'df -h; free -m' < /dev/null
echo /dev/null | cat - $var.txt
fi
How can I capture the result and save to a file, I tried echo but it didn't work.
Thank You
< /dev/null
Binds the process standard output with /dev/null file.
Saving a < /dev/null result ...
/dev/null is a file and does not produce a result.
ssh root#$192.168.80.180 'df -h; free -m' < /dev/null
Runs ssh command with specified parameters and binds it's standard input with /dev/null.
/dev/null is a magic file. It always has size equal to zero. You can write anything to it. It's size still will be zero. You can't read anything from it. Cause it's size is always zero. Expression < /dev/zero is used to "close" a commands standard input.
For saving a process output it produces on standard output you can use command substitution:
var=$(ssh root#$192.168.80.180 'df -h; free -m' < /dev/null)
or you can save the standard output of a process by binding it with a file:
ssh root#$192.168.80.180 'df -h; free -m' < /dev/null > result.txt
You can read more about bash redirections.
The line
if [ $(nc -z 192.168.80.180 22; echo $?) -eq 0 ]; then
is just strange. Just:
if nc -z 192.168.80.180 22; then
The if expression is true if the command returns zero status. Comparing commands return status with zero [ $(command; echo $?) -eq 0 ] is just strange.
More about if.
Also always quote any expansions (unless you know you don't have to). So quote command substitution [ "$(nc -z ...; echo $?)" -eq 0 ].

Testing grep output in bash

I'm looking for a way to act differently in my bash script depending on my external IP address. To be more specific if my external IP address is 111.111.111.111 then do some action, otherwise do something else.
This is my code:
extIP=$(curl ifconfig.me | grep 111.111.111.111)
if [ -? ${extIP} ]
then
runExecutable
else
echo "111.111.111.111 is not your IP."
fi
I don't know how to test extIP.
Try this
echo 'Enter the IP Address you want to test'
read IP_ADDRESS
extIP=$(curl ifconfig.me | grep -o '[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*')
if [ "$IP_ADDRESS" == "$extIP" ]
then
bash runExecutable.sh
exit 0
fi
echo '${IP_ADDRESS} is not your IP'
exit 1 # if it's bad or just exit 0 otherwise
Just run the curl/grep command the check the exit status
curl ifconfig.me | grep -s 111.111.111.111
if [ "$?" == "0" ]
then
runExecutable
else
echo "111.111.111.111 is not your IP."
fi
You should test the exit code of your command pipeline directly with if, like this:
addr="111.111.111.111"
if curl -s ifconfig.me | grep -qF "$addr"; then
runExecutable "$addr" ...
else
echo "$addr is not your IP."
fi
Also, you probably want to silence the curl's progress output with -s and grep's matches with -q, use a fixed string search in grep with -F, and store your IP address in a variable for easier later reuse.
Note the [ is actually a shell command that exits with 0 if condition that follows is true, or with 1 otherwise. if than uses that exit code for branching.
See this bash pitfall for more details.

Bash/ syntax error near unexpected token `}'

I have the following code in bash:
check_port()
{
local host=${1}
local port=${2}
echo $host
echo $port
while true; do
if nc -w 5 -z 127.0.0.1 111 && nc -w 5 -z 127.0.0.1 5001 ;
then
echo -e "\a\n => Port at host is open"
break
else
echo -e "\a\n => Port at host is closed"
break
fi
}
For some reason, I get the following error:
syntax error near unexpected token `}'
`}'
I don't understand why: } is closing the scope of the function.
You need a done line to terminate your while loop, between the fi line and the closing brace }.
However, I'm not entirely certain why you even have the while true loop since you break out of it regardless of whether the if evaluates to true or false. Hence it's superfluous, unless you plan on changing the behaviour of one of those blocks at some point.
If you're not planning that, you're better off with the much simpler:
check_port()
{
local host=${1}
local port=${2}
echo $host
echo $port
if nc -w 5 -z 127.0.0.1 111 && nc -w 5 -z 127.0.0.1 5001 ; then
echo -e "\a\n => Port at host is open"
else
echo -e "\a\n => Port at host is closed"
fi
}
I'm also not entirely certain of the sanity of passing in the host and port and then ignoring them (instead using localhost with two hard-coded ports).
However, I'm going to assume that you're still in the development/testing phase rather than the possibility you've gone insane :-)
The done at the end of the while loop is missing
....
break
fi
done;
}

Resources