Bash: Loop until command exit status equals 0 - linux

I have a netcat installed on my local machine and a service running on port 25565. Using the command:
nc 127.0.0.1 25565 < /dev/null; echo $?
Netcat checks if the port is open and returns a 0 if it open, and a 1 if it closed.
I am trying to write a bash script to loop endlessly and execute the above command every second until the output from the command equals 0 (the port opens).
My current script just keeps endlessly looping "...", even after the port opens (the 1 becomes a 0).
until [ "nc 127.0.0.1 25565 < /dev/null; echo $?" = "0" ]; do
echo "..."
sleep 1
done
echo "The command output changed!"
What am I doing wrong here?

Keep it Simple
until nc -z 127.0.0.1 25565
do
echo ...
sleep 1
done
Just let the shell deal with the exit status implicitly
The shell can deal with the exit status (recorded in $?) in two ways, explicit, and implicit.
Explicit: status=$?, which allows for further processing.
Implicit:
For every statement, in your mind, add the word "succeeds" to the command, and then add
if, until or while constructs around them, until the phrase makes sense.
until nc succeeds; do ...; done
The -z option will stop nc from reading stdin, so there's no need for the < /dev/null redirect.

You could try something like
while true; do
nc 127.0.0.1 25565 < /dev/null
if [ $? -eq 0 ]; then
break
fi
sleep 1
done
echo "The command output changed!"

Related

Bash script that check connection with host:port using telnet and netcar

I have task to create quite complicated bash script, which at first part is checking the connection to host:port with telnet and- if telnet would fail or would not be installed, try the connection using netcat.
I have problem with loop, where it will skip netcat if telnet would connect to the host and also, if both- telnet and netcat would fail- script would finish with error message.
The script:
#!/bin/bash
echo Type host IP address
read REMOTEHOST
echo Type port number
read REMOTEPORT
TIMEOUT=5
echo quit | timeout --signal=9 5 telnet $REMOTEHOST $REMOTEPORT
if nc -w $TIMEOUT -z $REMOTEHOST $REMOTEPORT; then
echo "I was able to connect to ${REMOTEHOST}:${REMOTEPORT}"
else
echo "Connection to ${REMOTEHOST}:${REMOTEPORT} failed. Exit code from Netcat was ($?)."
fi
You can use the $? variable to get the exit code from the last command.
I found that your original telnet command exits with error code 1 on my system because the escape character is ^]. When I telnet manually I need to hit ctrl-] to enter the telnet prompt, then I can enter 'quit'.
The trick here is you cannot just type ^], you have to type ctrl-v ctrl-]
ctrl-v tells the system to capture the next ctrl character.
The following gives me an exit code of 0, and you can verify by running it manually with echo $? at the command line
-- remember to use ctrl-v ctr-]
$ (echo ^]; echo quit) | timeout --signal=9 5 telnet <REMOTEHOST> <REMOTEPORT>
$ echo $?
Then you can use this in your script:
#!/bin/bash
echo Type host IP address
read REMOTEHOST
echo Type port number
read REMOTEPORT
TIMEOUT=5
(echo ^]; echo quit) | timeout --signal=9 5 telnet $REMOTEHOST $REMOTEPORT > /dev/null 2>&1
TELNET_EXIT_CODE=$?
if [[ $TELNET_EXIT_CODE -ne 0 ]]; then
nc -w $TIMEOUT -z $REMOTEHOST $REMOTEPORT > /dev/null 2>&1
NC_EXIT_CODE=$?
fi
if [[ $TELNET_EXIT_CODE -eq 0 ]] || [[ $NC_EXIT_CODE -eq 0 ]]; then
echo "success"
else
echo "fail"
fi
Tested on Ubuntu 20.04.04, GNU bash version 5.0.17, Telnet version 0.17-41

SSH Remote command exit code

I know there are lots of discussions about it but i need you help with ssh remote command exit codes. I have that code:
(scan is a script which scans for viruses in the given file)
for i in $FILES
do
RET_CODE=$(ssh $SSH_OPT $HOST "scan $i; echo $?")
if [ $? -eq 0 ]; then
SOME_CODE
The scan works and it returns either 0 or (1 for errors) or 2 if a virus is found. But somehow my return code is always 0. Even, if i scan a virus.
Here is set -x output:
++ ssh -i /home/USER/.ssh/id host 'scan Downloads/eicar.com; echo 0'
+ RET_CODE='File Downloads/eicar.com: VIRUS: Virus found.
code of the Eicar-Test-Signature virus
0'
Here is the Output if i run those commands on the "remote" machine without ssh:
[user#ws ~]$ scan eicar.com; echo $?
File eicar.com: VIRUS: Virus found.
code of the Eicar-Test-Signature virus
2
I just want to have the return Code, i dont need all the other output of scan.
!UPDATE!
It seems like, echo is the problem.
The reason your ssh is always returning 0 is because the final echo command is always succeeding! If you want to get the return code from scan, either remove the echo or assign it to a variable and use exit. On my system:
$ ssh host 'false'
$ echo $?
1
$ ssh host 'false; echo $?'
1
$ echo $?
0
$ ssh host 'false; ret=$?; echo $ret; exit $ret'
1
$ echo $?
1
ssh returns the exit status of the entire pipeline that it runs - in this case, that's the exit status of echo $?.
What you want to do is simply use the ssh result directly (since you say that you don't want any of the output):
for i in $FILES
do
if ssh $SSH_OPT $HOST "scan $i >/dev/lull 2>&1"
then
SOME_CODE
If you really feel you must print the return code, that you can do that without affecting the overall result by using an EXIT trap:
for i in $FILES
do
if ssh $SSH_OPT $HOST "trap 'echo \$?' EXIT; scan $i >/dev/lull 2>&1"
then
SOME_CODE
Demo:
$ ssh $host "trap 'echo \$?' EXIT; true"; echo $?
0
0
$ ssh $host "trap 'echo \$?' EXIT; false"; echo $?
1
1
BTW, I recommend you avoid uppercase variable names in your scripts - those are normally used for environment variables that change the behaviour of programs.

Detecting when Mongod's port is open inside a script

I'm trying to write a bash script that starts a mongod process, waits for it to start (i.e. have it's default port open) and then pipe some commands into it through the mongo shell. I'd like some way to wait for the mongod process to be completely up that's more deterministic than just sleep 5.
This is the script so far:
set_up_authorization() {
echo "Setting up access control"
/path/to/mongo < configure_access_controls.js
}
wait_for_mongod_to_start() {
RETRIES=1000
CONNECTED="false"
echo "Waiting for mongod to start"
while [[ $RETRIES -ge 0 && $CONNECTED == "false" ]] ; do
RESPONSE=$(exec 6<>/dev/tcp/127.0.0.1/27017 || echo "1")
if [[ $RESPONSE == "" ]] # which should happen if the exec is successful
CONNECTED="true"
fi
RETRIES=$((RETRIES - 1))
done
if [[ $RETRIES -eq 0 ]] ; then
echo "Max retries reached waiting for mongod to start. Exiting."
exit 1
fi
echo "Mongod started"
}
./start_mongod_instance.sh
wait_for_mongod_to_start
set_up_authorization
While this script works, it produces a ton of output on the terminal while the exec is failing:
./initialize_cluster.sh: connect: Connection refused
./initialize_cluster.sh: line xx: /dev/tcp/127.0.0.1/27017: Connection refused
...which repeats for all ~900 failed attempts.
Neither of the following seems to get rid of the terminal logging either:
exec 6<>/dev/tcp/127.0.0.1/27017 >/dev/null
OR
exec 6<>/dev/tcp/127.0.0.1/27017 2>/dev/null
I've also tried using the following:
ps -aux | grep "mongod" | wc -l
but the process having a pid that ps lists isn't equivalent to it's port being open or it accepting connections.
Any ideas on either front would be appreciated - a more elegant way to wait for the process to start completely or a way to get rid of the excessive logging to the terminal.
Note: I don't have access to nmap or nc to check the port (this is on a client's machine).
exec is a bit special. It affects the output of the current shell. Meaning you need to redirect stderr of the current shell before running the port check:
host="localhost"
port="9000"
exec 2>/dev/null # redirect error here
while ! exec 3<>"/dev/tcp/${host}/${port}" ; do
echo "Waiting ..."
sleep 1
done
Furthermore you might have noticed that I check the exit status of exec rather than some output to decide whether the port is open or not.
If you want to reset it afterwards:
host="localhost"
port="9000"
# Copy fd 2 into fd 3 and redirect fd 2 to /dev/null
exec 3<&2 2>/dev/null
while ! exec 3<>"/dev/tcp/${host}/${port}" ; do
echo "Waiting ..."
sleep 1
done
# Copy back fd 3 into fd 2
exec 2<&3
echo "EE oops!" >&2

Linux script to monitor remote port and launch script if not successful

RHEL 7.1 is the OS this will be used on.
I have two servers which are identical (A and B). Server B needs to monitor a port on Server A and if it's down for 30 seconds, launch a script. I read netcat was replaced with ncat on RHEL 7 so this is what I have so far:
#!/bin/bash
Server=10.0.0.1
Port=123
ncat $Server $Port &> /dev/null; echo $?
If the port is up, the output is 0. If the port is down, the output is 1. I'm just not sure on how to do the next part which would be "if down for 30 seconds, then launch x script"
Any help would be appreciated. Thanks in advance.
If you really want to script this rather than using a dedicated tool like Pacemaker as #CharlesDuffy suggested, then you could do something like this:
Run an infinite loop
Check the port
If up, save the timestamp
Otherwise check the difference from the last saved timestamp
If more time passed then threshold, then run the script
Sleep a bit
For example:
#!/bin/bash
server=10.0.0.1
port=123
seconds=30
seen=$(date +%s)
while :; do
now=$(date +%s)
if ncat $server $port &> /dev/null; then
seen=$now
else
if ((now - seen > seconds)); then
run-script && exit
fi
fi
sleep 1
done
#!/bin/bash
Server=10.0.0.1
Port=123
port_was_down=0
while true; do
sleep 30
if ! ncat $Server $Port &> /dev/null; then
if [[ $port_was_down == "1" ]]; then
run-script
exit
else
port_was_down=1
fi
else
port_was_down=0
fi
done
what about using nmap?
something like:
TIMEOUT=30s;
HOST=10.0.0.1;
PORT=123;
if nmap --max-rtt-timeout $TIMEOUT --min-rtt-timeout $TIMEOUT -p $PORT $HOST | grep "^$PORT.*open"; then
echo 'OPEN';
else
echo 'CLOSED';
fi;

Bash exit status when using while loop [duplicate]

This question already has answers here:
How to get the exit status a loop in bash
(8 answers)
Closed 7 years ago.
I have a bash script that goes through a list of ip's and pings them one by one. If the exit status for each ping is 0, then echo that the node is up, else the node is down.I am able to get this to work perfectly, but when the bash script ends the exit status is always 0.
What I am trying to achieve is for example out of 5 ip's if the 3rd one fails, to continue through the list and check the rest but once the script ends throw an exit status other than 0 and output which ip has failed.
cat list.txt | while read -r output
do
ping -o -c 3 -t 3000 "$output" > /dev/null
if [ $? -eq 0 ]; then
echo "node $output is up"
else
echo "node $output is down"
fi
done
thanks in advance!
Your first problem is that by doing cat file | while read you've spawned the while in its own subshell. Any variables it sets will only exist during that loop, so persisting a value will be difficult. More info on that issue here.
If you use while read ... done < file it will work correctly. Make an exit status flag that defaults to zero, but set it to one if any errors occur. Use it as your script's exit value.
had_errors=0
while read -r output
do
ping -o -c 3 -t 3000 "$output" > /dev/null
if [ $? -eq 0 ]; then
echo "node $output is up"
else
echo "node $output is down"
had_errors=1
fi
done < list.txt
exit $had_errors

Resources