Bash script to ping host with while condition - bash

First, I would like to say that I know nothing about bash but I am trying to learn through practice.
So, I am trying to make a script which will send a magic packet to a remote host. While the remote host is starting I would like to print dots on the display.
I really don't have a problem with the wakeonlan part and of course I don't really need a script to do that. However, in order to learn something useful I try to make a script.
So my code is:
#!/bin/bash
! timeout 0.5 ping -c1 8.8.8.8 > /dev/null 2>&1
if [ $? -eq 1 ]
then
echo "Host is up"
else
wakeonlan FF:FF:FF:FF:FF:FF
echo "Host is starting ..."
while ! ping -c1 -w1 -n 8.8.8.8 > /dev/null 2&>1
do
printf "%c" "."
done
echo "Host is up!\n\n"
fi
exit 0
Now when the host is already up the while loop exits without printing. But when the host is down it outputs infinetely dots on the display, even if the host is up.
I really don't understand why the while loop does not stop when the condition is met.
I would appreciate your answer, especially if its aligned with my implementation.

Ok, I think I've figured out what's going on here. In the command
while ! ping -c1 -w1 -n 8.8.8.8 > /dev/null 2&>1
the & and > are in the wrong order. This causes a chain reaction of confusion and unexpected interpretations.
Specifically, bash parses 2&>1 as the argument "2" followed by the redirect &>1 which sents both stdout and stderr into a file named "1". So it runs this command, with all output to "1":
ping -c1 -w1 -n 8.8.8.8 2
Some versions of ping will give an error/usage message here, because they only accept a single target IP address (and then exit with an error status, making your loop run forever).
Other versions will interpret 8.8.8.8 as a hop to use on the way to the final destination 2, which is an old shorthand for the IP address 0.0.0.2. Which doesn't actually exist on the Internet. Which means the ping will fail and exit with an error status, making your loop run forever.
I suspect if you look in the file "1", you'll be able to tell which of these (or possibly something else) is happening.
There are some scripting lessons to be learned here:
All those cryptic piles of symbols matter, and if you get them a little bit wrong you can wind up doing something very different from what you intended.
Discarding output (especially error output) is a good way to hide what's going wrong. If a script is having trouble, capture & examine any errors its components produce.
set -x tells the shell to print what it thinks it's executing as it runs commands, and putting that before problem sections in scripts (and set +x afterward to turn it off) is a good say to find out what's going on. This is how I figured out that bash was treating "2" as an argument rather than part of the redirect.
shellcheck.net is handy!

Related

How to use ping -f until file = false

I would like to execute the ping -f command until cat ~/.test = false, but
until [[ `cat ~/.test` == false ]]; do sudo ping -f 10.0.1.1; done
only checks one time. How to kill command automatically when the file changes?
This approach will not work for two reasons:
The ping command runs until it is interrupted. In other words: There will only be one loop iteration ever, because you will be stuck in the loop.
cat ~/.test will always be "true" (i.e. successful), as long as the file exists. It will only be "false" (i.e. exit with a non-zero error code), if the file does not exist (any more). cat is not suited for checking file changes - unless that change is creating or deleting the file.
With that in mind, you should probably try something along these lines:
#!/bin/bash
# launch the ping process and leave it running in the background
ping -f 10.0.1.1 &
# get the process ID of the previous command's process
PING_PID=$!
# until the file ~/.test does not exist any more,
# do the stuff in the loop ...
until ! test -f ~/.test; do
# sleep for one second
sleep 1
done
# kill the ping process with the previously stored process ID
kill $PING_PID
The script is untested and may not work completely, but it should give you an idea how to solve your problem.
Edit:
If it does not need to be a flood ping, you can use this simpler script:
#!/bin/bash
# As long as the file ~/.test exists,
# send one ping only to the target.
while test -f ~/.test; do
ping -c 1 10.0.1.1
done
This approach was suggested by twalberg.
Another advantage of this approach (besides the simpler script) is that you do not need to sudo the ping command any more, because unlike flood pings the "normal" pings do not need root privileges.

Change the behaviour depending on whether you are in local network or not

I am making bash script like this
if [ "if I am in local "]; then
ssh myuser#192.168.11
else
ssh myuser#myserver.com
fi
How could I get if I am in local or not?
One possibility is to test whether 192.168.11 is ping-able:
if ping -qc1 192.168.11 >/dev/null; then
ssh myuser#192.168.11
else
ssh myuser#myserver.com
fi
If ping receives a response from 192.168.11, then it exits with return code 0 (success) and the then clause is executed. If no responses are returned, the else clause is executed.
The option -c1 tells ping to only send one packet. If your local network is unreliable (unlikely), you may need to send more than one packet.
The -q option tells ping to be a bit more quiet but that doesn't really matter because we dump its output into /dev/null.
Note that IP addresses are typically written with 4 values, not 3: a number may be missing from 192.168.11.
Documentation
From man ping:
If ping does not receive any reply packets at all it will exit with
code 1. If a packet count and deadline are both specified, and fewer
than count packets are received by the time the deadline has arrived,
it will also exit with code 1. On other error it exits with code
2. Otherwise it exits with code 0. This makes it possible to use the exit code to see if a host is alive or not.
This works with assumption then IP addresses starting with 10,127,197,172 are reserved IP for private network.
myIp=$(hostname -I) # or i based on your version.
echo $myIp |grep -q -P '(^127\.)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^192\.168\.)'
if [ "$?" -eq 0 ];then
echo "Private Network"
else
echo "Over Internet"
fi
Inspiration from Here .

If telnet output contains something execute action

I am trying to write a small script in bash. My target is that a Phone Call on my FritzBox will mute or pause my TV.
I get the information of a call via telnet (telnet fritz.box 1012) on my RaspberryPi and if there is coming a phone call in I get this output:
24.08.15 14:03:05;RING;0;017mobilephonenumber;49304myhomenumber;SIP0;
The only Part that is every time the same is the RING before the calling number. What I need is a script that checks the output of the telnet and if in the telnet output is a ring than execute an action in my case i just need to do a http request on an internal site or start another script.
This is what I have tried is this:
#!/bin/bash
#string='echo "My string"'
string=$(telnet –e p fritz.box 1012)
for reqsubstr in 'alt' 'RING';do
if [ -z "${string##*$reqsubstr*}" ] ;then
echo "String '$string' contain substring: '$reqsubstr'."
else
echo "String '$string' don't contain substring: '$reqsubstr'."
fi
done
But I don`t get the output of my telnet session into the string. Anyone who can help me?
After you latest comment, and reading your question again, I think we can go for something simpler based on nc (netcat).
Let assume we create a bash script called action.sh
#!/bin/bash
while true;
do
read logline
for substr in 'alt' '\;RING\;'
do
if [[ "$logline" = *${substr}* ]]; then
echo "Got a match"
fi
done
done
Make the script executable, chmod +x action.sh, then start it with nc as follow:
nc -l fritz.box 1012 | ./action.sh
It will listen infinitely for incoming traffic from the box and should act as you want. I don't have your box obviously and I tested by starting it as nc -l 127.0.0.1 12000 and then provided input via telnet 127.0.0.1 12000, providing your RING sample. It seems to work.
$ nc -l 127.0.0.1 12000 | ./action.sh
Got a match
017mobilephonenumber
Got a match
017mobilephonenumber
Would this be more acceptable to you ? without expect (I am not an expect wizard anyway).
I hope this answer will help you more.
do you have access to expect? It is meant exactly for such kind of scenario. man expect is your friend, and I found a fair tutorial via google. If you google for modem expect you can find sample script for such scenario (I can find some for FritzBox too but not sure it's the same box as yours). I haven't used it in ages, though.
Here my short conclusion:
To see incomming calls on your FritzBox you can simply execute:
telnet fritz.box 1012
in your terminal.
The output should look like that:
24.08.15 14:03:05;RING;0;017mobilephonenumber;49304myhomenumber;SIP0;
I my case I want the execute an command the mutes my TV.
Here are the instructions how I controll my TV via Web/RaspberryPi (Philips Harmony Hub) if some one is interested.
To execute a command on an incomming call create a script called action.sh
#!/bin/bash
while true;
do
read logline
for substr in 'alt' '\;RING\;'
do
if [[ "$logline" = *${substr}* ]]; then
echo "Got a match"
#here you can put the command you want to execute on an incoming call
fi
done
done
After you create it make it executable using chmod +x action.sh
After that you can start the script using nc 192.168.1.1 1012 | action.sh
And that's how you execute a script on an incomming call!
Special thanks to tgo!!

Bash scripting assign ALL output from command to variable

I'm trying to get both successful and unsuccessful responses from a ping in a bash script but am unable to thus far.
My code looks like this...
ping_results=$(ping -c 4 -q google.com)
This works when the ping is successful, but if I don't have an internet connection and I get the result
ping: unknown host google.com
It is printed to the console, and my script appears to exit.
I want both the ping result or error to be stored in the ping_results variable.
Any help will be appreciated.
Okay, the simple answer to your question is to redirect stderr to stdout. as what Fredik Phil mentioned in the comments.
instead of:
ping_results=$(ping -c 4 -q google.com);
use:
ping_results=$(ping -c 4 -q google.com 2>&1);
or something similar...
However, depending on what you're doing, it might be better to test if the exit code of the ping command is 1 (indicating that the ping is ending in an error), or 0 (indicating that the ping is successful).
the exit code is stored in the variable "$?".

how to run scripts within a telnet session

I want to connect to a remote host using telnet
there is no username/password verification
just
telnet remotehost
then I need to input some commands for initialization
and then I need to repeat the following commands:
cmd argument
argument is read from a local file, in this file there are many lines, each line is a argument
and after runing one "cmd argument", the remote host will output some results
it may output a line with string "OK"
or output many lines, one of which is with string "ERROR"
and I need to do something according to the results.
basically, the script is like:
initialization_cmd #some initial comands
while read line
do
cmd $line
#here the remote host will output results, how can I put the results into a variable?
# here I want to judge the results, like
if $results contain "OK";then
echo $line >>good_result_log
else
echo $line >> bad_result_log
fi
done < local_file
the good_result_log and bad_result_log are local files
is it possible or not? thanks!
This won't work as echo will output to the stdout of the tty and not to the stdin of the telnet process.
I would suggest writing an expect script for this task. Perhaps you could adapt something like this.
This question was asked in at least four different forums at the same time. Don't know what kind of points this kind of entrepreneurship earns, but here are links to answers:
linux forums
unix.com
superuser.com

Resources