2

I am trying to ping several IP addresses / hostnames, and determine whether any of them respond.

Starting fromthis question, I'm using theecho host1 host2 host3 | xargs -n1 -P0 ping -c1 -w2 approach.

My problem is that ping will return zero if the ping succeeds and non-zero (actually 1) if the ping fails.

This means, that the overall command returns 123 if any of the pings fail, and 0 if all pings succeed, which isn't what I want.

I can write a hacky script like:

#!/bin/bashping "$@"exit $(( 1 - $? ))

And then use that script in the xargs parameters instead of the realping, which achieves what I want, but feels very messy.

Is there a better way to achieve what I need, without having to install non-standard ping tools, or have hacky external scripts.

Asxargs is a command, I don't think I can use a shell function in place of my external script either, which would have given a slightly more elegant option (allowing one script to hold all the code, rather than needing a second script to call from the script where I am callingxargs from.

asked2 days ago
Michael Firth's user avatar
1
  • 4
    Did you ever consider using something likefping instead ofping?Commented2 days ago

6 Answers6

7

You don't need the separate script (or the bash shell). Shell code can also be given as argument with the-c option (that's what thesystem("shell code") function of most languages use):

if  echo host1 host2 host3 |    xargs -n1 -P0 sh -c '! "$0" "$@"' ping -c1 -w2then  echo 'They all failed'else  echo 'At least one succeeded'fi

In:

sh -u -ce -o xtrace -- 'shell code' 'script name' 'arg 1' 'arg 2'

(here-u,e and-o xtrace to show you can have more options before or after-c)

  • the first non-option argument (hereshell code) is the code to interpret
  • the second (script name) a name you want to give to that inline script (the equivalent of the file name for a script in a file). It has these effects:
    • it's used for instance in error messages that the shell generates to tell the user the error is coming from that script.
    • it's what the$0 special parameter expands to.
  • and the remaining arguments make up the arguments of the inline script, so are available in theshell code as$1,$2... with"$@" the verbatim list of them all.

So, here withsh -c '! "$0" "$@"' ping -c1 -w2, we're creating an inline script which we callping.xargs will pass-c1,-w2 and one word read from stdin at a time as arguments to that script, and that script runs the command calledping with those arguments and the exit status reversed with!.

You may argue that shell error messages if any showing as "ping" error messages, may be confusing to the user, so you could change it tosh -c '! "$@"' sh ping -c1 -w2 for the inline script's name to besh instead.

But here I'd argue that to scan networks, you'd rather use things likenmap thanping in a loop.

answered2 days ago
Stéphane Chazelas's user avatar
3
  • Great - I didn't know about using "sh -c" in this way.Commented2 days ago
  • You've used"$0" in the description, but not in the code block - typo in the latter?Commented16 hours ago
  • @TobySpeight. Thanks. Looks like I had messed up one of my edits. Fixed now.Commented13 hours ago
7

I don't think I've ever seen aping tool that allows multiple targets on the command line, and it's mostly aimed for interactive use anyway.

On the other hand,fping is pretty much made for scripting and allows pinging multiple targets in parallel. Of course, it also exits with a falsy status if any of the targets fail to answer, but it can also give you a list of the ones thatdid answer as output. If there's any output, someone answered.

So, e.g.

ping_any() {    # truthy if the output is not empty    [ "$(fping -a -r1 "$@")" ]}if ping_any somehost otherhost...; then    echo someone answeredfi

(adjust the retries and time limits with-t,-r and-B to taste)

answered2 days ago
ilkkachu's user avatar
1
  • I wasn't wanting to install any extra packages, but this is a good solution if installingfping is OK.Commentedyesterday
2

If you're doing network analysis on that machine, you might as well use a bit more network-analystic tools. Installmtr-packet (often in themtr software package). You need to resolve your hostnames first, but that's a good idea anyways, because if you want to check whetherany host succeeds, and a single hostname has multiple A records, then ping only tries one, and not the others, too, even if the first fails.

So, here goes:

#!/bin/bash# let's assume we have all the hostnames we care about in# a file called names.hostsparallel -j1 -m dig +short :::: names.hosts > addresses.hostscounter=1while IFS=$'\n' read -r addr; do  printf '%d send-probe ip-4 %s timeout 2\n' ${counter} "${addr}"  counter=$((counter + 1))done < addresses.hosts \  | mtr-packet \  | grep '^[0-9]* reply'

You'll really to readmtr-packet's man page, as aside fromicmp (which is whatping does), it also supports other probeprotocols, and can set quite some other options.

For compactness, the wholewhile… thing could be replaced withawk orsed = | sed 'N;s/\n/ /' rather straightforwardly. I just didn't feel like discussing the more obscuresed commands.

parallel -j1 -m dig +short :::: names.hosts \  | sed = \  | sed 'N;s/\n/ send-probe timeout 2 ip-4 /' \  | mtr-packet
answered2 days ago
Marcus Müller's user avatar
3
  • 1
    This isn't for network analysis, just verifying that creating a (group of) VMs won't create an IP conflict. Hence not wanting to install custom tools.Commented2 days ago
  • absolutely understandable! Do note that this sounds like a pretty bad way of avoiding conflicts, because not all other machines have to be running all the time, nor do all machines react to pings; so you might still cause a conflict, once one of these machines comes back online. If these other hosts are all just your local VMs, then there's an argument to be made that you should assign IP addresses exclusively, instead of trying to avoid conflicts "in hindsight". Typical VM orchestration solves that issue either through things like DHCP or other lease logic.Commented2 days ago
  • I agree it is imperfect, but it will detect a reasonable percentage of cases. The environment this is used in doesn't support the better ways of doing this and isn't completely within my control.Commentedyesterday
1

My problem is that ping will return zero if the ping succeeds and non-zero (actually 1) if the ping fails.

That's usual UNIX shell command behaviour! a return value of 0 signals success, anything else failure. So, that seems right?

This means, that the overall command returns 123 if any of the pings fail, and 0 if all pings succeed, which isn't what I want.

You could instead ofxargs useparallel (thanks @OleTange!), it supports cancelling on a count of failed or successful tasks with the--halt (short version of--halt-on-error) option:

echo host1 host2 host… | parallel -P 0 -n 1 --halt now,success=1 --joblog=jobs.log ping -c1 -w2# or, simpler without the echo & pipeparallel -P 0 -n 1 --halt now,success=1 --joblog=jobs.log ping -c1 -w2 ::: host1 host2 host…

now means that when the condition (after the comma) is achieved to stop all other processes immediately (alternative issoon, which lets them finish but doesn't spawn new ones).success=1 means the condition is the successful completion of at least 1 started process.

Now you have a joblog, which is a tab-separated file. So,

# strip off first line and get seventh, ninth column# then look for columns that start with 0 and <jobs.log tail -n+2 \  | cut -f7,9 \  | grep -q '^0'

This returns success if there's any line with a 0 exit status, and failure if there isn't. You could of course also do more fancy stuff with the joblog and see which hosts actually responded.

answered2 days ago
Marcus Müller's user avatar
1
  • 1
    My systems don't haveparallel installed, so sticking withxargs works better for me.Commented2 days ago
0

How about a simplefor loop (I'm assumingbash, I am unfamiliar with other shells):

for x in host1 host2 host3do    ping -c1 -w2 -q $x && echo $x OK || echo $x Faileddone

Which pings each host and reports success or failure.

This answer shows how to embed the loop in anxargs command.

answered2 days ago
Peter Bill's user avatar
4
  • 2
    That's morezsh syntax. In bash, leaving a variable unquoted is asking the shell to split it on values of$IFS (and perform globbing on the result) which doesn't make sense here.Commented2 days ago
  • Using&&|| in place of a properif statement is also generally considered bad practice. Herecho failed will also be run ifecho OK fails.Commented2 days ago
  • 3
    The problem with sequential testing is it can take a long time even with a relatively small number of hosts. '-w2' means that for 10 hosts the script will wait at least 20s, versus 2s if doing them in parallelCommented2 days ago
  • 1
    xargs -P0 runs commands in parallel with unlimited parallelism. The question didn't mention this, but is an important part of the behaviour they want to replicate. I didn't see the point of overcomplicating things withxargs sh -c either until I saw this comment and looked more closely at @MichaelFirth's original commend. GNUparallel is an easier-to-use tool which is worth installing if you do lots of this kind of thing, or there'smake -j 0 if you can turn your task into a Makefile with parallel recipes... Butxargs -P0 is fairly useful.Commentedyesterday
0

Sequential testing rather than parallel

#!/bin/bash#hosts=( 192.0.2.1  198.51.100.2  1.1.1.1  203.0.113.3 )for host in "${hosts[@]}"do    ping -c1 -i0.3 -w1 "$host" && breakdoneif [ "$?" -eq 0 ]then    echo "One of the hosts ($host) successfully responded" >&2fi

You can encapsulate this in a function, and it wouldn't be too hard to write the first responding host name tostdout if required.

pingAny(){    local host    for host in "$@"    do        ping -q -c1 -i0.3 -w1 "$host" >/dev/null 2>&1 && break    done}if pingAny 192.0.2.1 198.51.100.2 1.1.1.1 203.0.113.3then    echo "One of the hosts successfully responded" >&2fi
answered2 days ago
Chris Davies's user avatar
1
  • 2
    The problem with sequential testing is it can take a long time even with a relatively small number of hosts. '-w1' means that for 10 hosts the script will wait at least 10sCommented2 days ago

You mustlog in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.