Ping hundreds in one script - bash

I want to ping some servers on a game, they are all in the same format, only there are possibly hundreds of them. This is what I currently use:
ping server1.servername.com -n 20 | grep Minimum | awk '{print $3}' | sed s/,// >> Output.txt
That will ping the server 20 times, and chop off everything but the minimum ping amount. If I wanted to ping 300 of these servers, I would have to paste that same line 300 times... Is it possible to have it specify just something like 1-300 in one line without needing 300 lines of the same thing?

rojo#aspire:~$ help for
<snip...>
for ((: for (( exp1; exp2; exp3 )); do COMMANDS; done
Arithmetic for loop.
Equivalent to
(( EXP1 ))
while (( EXP2 )); do
COMMANDS
(( EXP3 ))
done
EXP1, EXP2, and EXP3 are arithmetic expressions. If any expression is
omitted, it behaves as if it evaluates to 1.
Try something like this:
for (( x=1; $x<=300; x++ )); do ( ping server$x.servername.com -n 20 | grep Minimum | awk '{print $3}' | sed s/,// >> Output.txt ); done
Update:
Here's the hackish idea I mentioned in my comments to this answer below. Caveat: I think my ping command must be different from yours. I'm composing this idea on a Debian machine.
Instead of -n count my ping syntax is -c count, and instead of a line containing "Minimum" I have "min/avg/max/mdev". So you might need to play with the grep syntax and so on. Anyway, with that in mind, modify the following as needed to perform a ping of each server in sequence from 1 to whatever until error.
#!/bin/bash
i=0
while [ $? -eq 0 ] && i=$(( i + 1 )); do (
echo -n "server$i min: "
ping server$i.servername.com -c 20 -i 0.2 | grep -P -o -e '(?<=\= )\d\.\d+'
); done
echo "n/a"
Basically in plain English, that means while exit code = 0 and increment i, echo the server name without a line break and ping it 20 times at 200ms interval, completing the echoed line with (scraping from the ping results) a decimal number preceded by an equal-space. (That pattern matches the minimum ping time result in the summary for Linux iputils ping.) If the ping fails, exit code will not equal 0 and the loop will break.

You can use loops:
while read line
do
ping $line.servername.com -n 20 | grep Minimum | awk '{print $3}' | sed s/,// >> Output.txt
done < servers_list

Sounds like a job for xargs, e.g.,
$ cat server-list | xargs -I% ping % -n 20 ...

Related

Counting all the 5 from a specific range in Bash

I want to count how many times the digit "5" appears from the range 1 to 4321. For example, the number 5 appears 1 or the number 555, 5 would appear 3 times etc.
Here is my code so far, however, the results are 0, and they are supposed to be 1262.
#!/bin/bash
typeset -i count5=0
for n in {1..4321}; do
echo ${n}
done | \
while read -n1 digit ; do
if [ `echo "${digit}" | grep 5` ] ; then
count5=count5+1
fi
done | echo "${count5}"
P.s. I am looking to fix my code so it can print the right output. I do not want a completely different solution or a shortcut.
What about something like this
seq 4321 | tr -Cd 5 | wc -c
1262
Creates the sequence, delete everything but 5's and count the chars
The main problem here is http://mywiki.wooledge.org/BashFAQ/024. With minimal changes, your code could be refactored to
#!/bin/bash
typeset -i count5=0
for n in {1..4321}; do
echo $n # braces around ${n} provide no benefit
done | # no backslash required here; fix weird indentation
while read -n1 digit ; do
# prefer modern command substitution syntax over backticks
if [ $(echo "${digit}" | grep 5) ] ; then
count5=count5+1
fi
echo "${count5}" # variable will not persist outside subprocess
done | head -n 1 # so instead just print the last one after the loop
With some common antipatterns removed, this reduces to
#!/bin/bash
printf '%s\n' {1..4321} |
grep 5 |
wc -l
A more efficient and elegant way to do the same is simply
printf '%s\n' {1..4321} | grep -c 5
One primary issue:
each time results are sent to a pipe said pipe starts a new subshell; in bash any variables set in the subshell are 'lost' when the subshell exits; net result is even if you're correctly incrementing count5 within a subshell you'll still end up with 0 (the starting value) when you exit from the subshell
Making minimal changes to OP's current code:
while read -n1 digit ; do
if [ `echo "${digit}" | grep 5` ]; then
count5=count5+1
fi
done < <(for n in {1..4321}; do echo ${n}; done)
echo "${count5}"
NOTE: there are a couple performance related issues with this method of coding but since OP has explicitly asked to a) 'fix' the current code and b) not provide any shortcuts ... we'll leave the performance fixes for another day ...
A simpler way to get the number for a certain n would be
nx=${n//[^5]/} # Remove all non-5 characters
count5=${#nx} # Calculate the length of what is left
A simpler method in pure bash could be:
printf -v seq '%s' {1..4321} # print the sequence into the variable seq
fives=${seq//[!5]} # delete all characters but 5s
count5=${#fives} # length of the string is the count of 5s
echo $count5 # print it
Or, using standard utilities tr and wc
printf '%s' {1..4321} | tr -dc 5 | wc -c
Or using awk:
awk 'BEGIN { for(i=1;i<=4321;i++) {$0=i; x=x+gsub("5",""); } print x} '

If i wanted to extract the ttl and display it from the ping command how could i go about doing that?

Scripting and want to ping devices on a network, tell me if it's reachable or not, and then get the ttl data from the ping and tell me the operating system.
Ive tried using the awk command, but I am also new to scripting and may not be using it correctly.
for host in $(seq 1 255);
do
ping -c 1 $sn.$host | grep "Unreachable" &>/dev/null
if [ $? -eq 0 ]; then
printf "%s\n" "$sn.$host is Offline"
fi
ping -c 1 $sn.$host | grep "ttl" &>/dev/null
if [ $? -eq 0 ]; then
printf "%s\n" "$sn.$host is Online"
fi
done
I need the ttl value and to store it into a variable and then tell me what operating system it is based on Linux has a ttl of 64, windows a ttl of 128, and ios of 255
You can do things in a bit more concise manner and minimize the time waiting for an Offline host by setting a timeout using the -w (or -W) option. For example you can save the ttl=XX value from ping in the same call that determines whether the host is online or not and then you can use a simple parameter expansion to extract the numeric ttl value from the right side of the equal sign, e.g.
ttlstr=$(ping -c1 -w1 $sn.$host | grep -o 'ttl=[0-9][0-9]*')
Above the command substitution $(...) executes ping and pipes the output to grep and assigns the results to ttlstr. The command substitution return is the return of the last command in the pipeline telling you whether grep for "ttl=####" succeeded or failed. That's all you need to determine whether the host is online or not. On failure output your "Offline" message and try the next, e.g.
## ping with 1 sec timeout store ttl=xx in ttlstr
ttlstr=$(ping -c1 -w1 $sn.$host | grep -o 'ttl=[0-9][0-9]*') || {
printf "%s is Offline\n" "$sn.$host"
continue;
}
If the command substitution succeeds, you can output your "Online" message and you can isolate the numeric ttl using a simple parameter expansion to remove all characters up to, and including, the '=' sign from the beginning of the string leaving only the numeric ttl, e.g.
ttl="${ttlstr#*=}" ## parameter expansion separating numeric ttl
printf "%s is Online, ttl=%d\n" "$sn.$host" "$ttl"
Putting it altogether you could do:
#!/bin/bash
sn=${1:-192.168.6}
for host in $(seq 1 255); do
## ping with 1 sec timeout store ttl=xx in ttlstr
ttlstr=$(ping -c1 -w1 $sn.$host | grep -o 'ttl=[0-9][0-9]*') || {
printf "%s is Offline\n" "$sn.$host"
continue;
}
ttl="${ttlstr#*=}" ## parameter expansion separating numeric ttl
printf "%s is Online, ttl=%d\n" "$sn.$host" "$ttl"
done
Example Use/Output
note: the sn is taken as the 1st argument to the program (using a default of 192.168.6 above)
$ bash ~/scr/utl/chksubnet.sh
<snip>
192.168.6.14 is Offline
192.168.6.15 is Offline
192.168.6.16 is Offline
192.168.6.17 is Online, ttl=64
192.168.6.18 is Offline
192.168.6.19 is Offline
<snip>
Look things over and let me know if you have further questions.
Here's a way of using awk to extract the ttl:
$ ping -c 1 8.8.8.8
PING 8.8.8.8 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: icmp_seq=0 ttl=53 time=48.575 ms
--- 8.8.8.8 ping statistics ---
1 packets transmitted, 1 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 48.575/48.575/48.575/0.000 ms
$ ping -c 1 8.8.8.8 | awk -F'[ =]' '/ttl/ {print $8 }'
53
The -F parameter tells awk what the field separator is. I've indicated spaces or the equals sign. The body of the awk script is written as pattern / command pairs. In the one line script, awk runs the print $8 command for any line that has ttl in line. The command tells awk to print the eigth field (remember, the -F indicated how to break the input line into fields. The /ttl/ pattern could be replaced with $7 == "ttl", too. This latter form is more accurate, since it would only match ttl if it appeared as the 7th field.
There are better more general implementations.
If you want to do this quickly, review the nmap utility. For example,
nmap -sn 192.168.1.0/24
Nmap aside, your program can be improved a bit by ping'ing the IP once and piping the results to awk, letting awk generate ALL the output.
Below is the script which you can use to find the Operating System name based on the ttl value if host is Online.
for host in $(seq 1 255);
do
ping -c 1 $sn.$host | grep "Unreachable" &>/dev/null
if [ $? -eq 0 ]
then
printf "%s\n" "$sn.$host is Offline"
continue
else
printf "%s\n" "$sn.$host is Online"
ttlValue=`ping -c 1 $sn.$host | grep ttl | awk '{print $6}'|awk -F'=' '{print $2}' &>/dev/null`
if [ $ttlValue -eq 64 ]
then
echo "Operating is Linux"
elif [ $ttlValue -eq 128 ]
then
echo "Operating is Windows"
else
echo "Operating is IOS"
fi
fi
done

Bash Script Compare and Run a Command

I have a shell script that gives me a txt file containing certain numbers.
For instance, " 48 347 345 221 1029 3943 1245 7899 " .
It only contains one line.
I want to trigger another shell script if one of those numbers exceeds 500.
How can and compare the numbers and run the shell script?
Thanks in advance
cat text.txt | if [ awk '{print $1}' -ge 500 ] then command.sh fi
Using awk you can try this:
awk '{for(i=1;i<=NF;i++)if($i > 500)exit 1}' text.txt || ./command.sh
command.sh will be executed if the awk command exits with a non-zero code because of the || operator.
Throw the file contents into an array -
$: declare -a lst=( $(<text.txt) )
Then run a quick loop. If you want to run the command for each hit,
$: for n in "${lst[#]}"
do (( n > 500 )) && echo "command.sh # $n > 500"
done
command.sh # 1029 > 500
command.sh # 3943 > 500
command.sh # 1245 > 500
command.sh # 7899 > 500
If you just want a quick and dirty version,
$: for n in $(<text.txt); do (( n > 500 )) && command.sh; done
But I recommend you take the time to do the horribly complicated 2 steps, lol
If you want to run it just once if any number is over 500,
$: for n in "${lst[#]}"
do (( n > 500 )) && { echo "command.sh # $n > 500"; break; }
done
command.sh # 1029 > 500
And don't use cat like that. Try to never use cat like that.
If you *REALLY needed the file as input to an if, then do this:
if condition
then action
fi < text.txt
You could use a simple for loop like this:
for n in $(cat test.txt)
do
if [ $n -gt 500 ]; then
something.sh
fi
done
You can simply :
[[ "48 347 345 221 500" =~ ([ ][5-9][0-9][0-9]|[ ][1-9][0-9]{3,}) ]] && echo "Hi"
Hi
[[ "48 347 345 221" =~ ([ ][5-9][0-9][0-9]|[ ][1-9][0-9]{3,}) ]] && echo "Hi"
Hope this helps!
When you don't want to convert the sring to numbers, it will become nasty.
Look for a number with at least 4 digits (first not 0), or a number with 3 digits, first 5 or higher will give
# incorrect, includes 500
grep -Eq "([1-9][0-9]|[5-9])[0-9][0-9]" text.txt && command.sh
This will also perform command.sh with number 500.
# incorrect, will fail for 5000 and 1500
grep -E "([1-9][0-9]|[5-9])[0-9][0-9]" text.txt | grep -qv 500 && command.sh
Fixing it becomes too complex. awk seems to be the most natural way.
An artificial way is converted the input to lines with one number and add an extra line with the border:
(echo "500 border"; tr ' ' '\n' < text.txt) |
sort -n |
tail -1 |
grep -qv "border" && command.sh

Bash idle session times greater than 15 min

I need help completing this. Trying to take user sessions sitting idle for greater than 15 minutes which aren't being kicked off by sshd_config and kill them. this is what I have to pull the sessions, how do I filter for greater than 15 minutes.
#!/bin/bash
IFS=$'\n'
for output in $(w | tr -s " " | cut -d" " -f1,5 | tail -n+3 | awk '{print $2}')
do
echo "$output \> 15:00"
done
If you are using Awk anyway, a shell loop is a clumsy antipattern. Awk already knows how to loop over lines; use it.
A serious complication is that the output from w is system-dependent and typically reformatted for human legibility.
tripleee$ w | head -n 4
8:16 up 37 days, 19:02, 17 users, load averages: 3.49 3.21 3.11
USER TTY FROM LOGIN# IDLE WHAT
tripleee console - 27Aug18 38days -
tripleee s003 - 27Aug18 38 ssh -t there screen -D -r
If yours looks similar, probably filter out anything where the IDLE field contains non-numeric information
w -h | awk '$5 ~ /[^0-9]/ || $5 > 15'
This prints the entire w output line. You might want to extract just the TTY field ({print $2} on my system) and figure out from there which session to kill.
A more fruitful approach on Linux-like systems is probably to examine the /proc filesystem.
You can try something like this …
for i in $(w --no-header | awk '{print $4}')
do
echo $i | grep days > /dev/null 2>&1
if [ $? == 0 ]
then
echo "greater that 15 mins"
fi
echo $i | grep min> /dev/null 2>&1
if [ $? == 0 ]
then
mins=$(echo $i | sed -e's/mins//g')
if [ $min -gt 15 ]
then
echo "Greater than 15 mins"
fi
fi
done
The tricky part is going to be figuring out what pid to kill.

Is this bash for loop statement correct?

Here's the code:
totLines=$(wc -l < extractedips.txt)
for((a=0;a!=$totLines;a++))
{
head -n$a extractedips.txt | nslookup >> ip_extracted.txt
}
I'm not sure what I'm doing wrong.
Yes! Despite what people are saying, this is a valid bash for loop!
Note, however, that it's a bash extension. It's not a valid sh for loop. You can not use this form of loop in e.g. a shell script declared with #!/bin/sh or run with sh yourscript.
The thing that doesn't work is your for loop contents.
You're trying to get the n'th line, but head -n 42 gets the first 42 lines, not line number 42.
You're using 0-based indexing, while head is 1-based.
You're piping to nslookup, but nslookup expects an argument and not stdin.
The shortest fix to your problem is:
totLines=$(wc -l < extractedips.txt)
for ((a=1; a<=totLines; a++)); do
nslookup "$(head -n "$a" extractedips.txt | tail -n 1)" >> ip_extracted.txt
done
However, the more efficient and canonical way of doing it is with a while read loop:
while IFS= read -r line
do
nslookup "$line"
done < extractedips.txt > ip_extracted.txt
You should use do and done instead of curly braces.
Like this:
totLines=$(wc -l < extractedips.txt)
for ((a=0; a!=totLines; a++)); do
head -n "$a" extractedips.txt | nslookup >> ip_extracted.txt
done
However, this code will do some weird stuff... Are you trying to pass it line by line into nslookup ?
What about this?
nslookup < extractedips.txt > ip_extracted.txt
Or you might want this:
while read -r line; do
nslookup "$line" >> ip_extracted.txt
done < extractedips.txt
Looks like you need sth. like this
for i in $(cat extractedips.txt)
do
echo $i | nslookup >> ip_extracted.txt
done
You don't need your counter variable, have a look at here for a better understanding.
Why I think your statement is wrong/not what you want and my answer don't deserve a negative voting :)
This statement will echo the first $a lines inside file extractedips.txt
head -n$a extractedips.txt | nslookup >> ip_extracted.txt
First up you have a file with ip's
cat extractedips.txt
127.0.0.1
192.168.0.1
173.194.44.88
Now we if you do
for a in 1 2 3
head -n$a
done
the script will output
127.0.0.1
127.0.0.1
192.168.0.1
127.0.0.1
192.168.0.1
173.194.44.88
But I guess you need a list with all all ip's only once. If you don't need duplicate ip's you could also remove them. This will give you a sorted list with duplicates removed.
for a in $(sort test.txt | uniq -u)
do
echo $a | nslookup >> ip_extracted.txt
done
Update
As Aleks commented this will not work if the text file contain a '*' char. The shell will expand all files in current directory and echo the filename. I didn't know that :/

Resources