Wait for Network Interface Before Executing Command
Asked Answered
T

5

13

I have a couple ideas on how I would achieve this. Not sure how I would script it.

Method 1: (probably the better choice)

Create a loop that pings a server until reply is received then execute command if no reply is received in X amount of time/runs continue script.

Method 2:

Check if network interface has valid IP then continue script

How would one go about adding this functionality in a script. Would awk or grep be of use in a situation like this? Thank you in advance for ANY input.

Transferor answered 9/3, 2014 at 3:5 Comment(3)
Wait for network interface to do what?Cosmopolis
to be up, but more than up lol I need an active connection where I'm sure I can connect to internet. I'd have it ping google or something for the testing.Transferor
It could be either. Something I can have be compatible regardless of interface name or typeTransferor
G
10

This command should wait until it can contact google or it has tried 50 times:

for i in {1..50}; do ping -c1 www.google.com &> /dev/null && break; done

The for i in {1..50} loops 50 times or until a break is executed. The ping -c1 www.google.com sends 1 ping packet to google, and &> /dev/null redirects all the output to null, so nothing is outputed. && break executes break only if the previous command finished successfully, so the loop will end when ping is successful.

Gerstein answered 9/3, 2014 at 3:20 Comment(7)
Could you explain each part of your script so I am not blindly using something without having an understanding of how it works? ;)Transferor
I also need it to stop trying if it can't contact google after X amount of tries.Transferor
What if it can't contact google, I still want it to continue script if it can't after it has tried so many times and fails. It's more to give it a chance to have a valid connection before attempting next part.Transferor
& the > /dev/null 2> /dev/null part isn't necessary it just keeps the output of terminal clean while running loop?Transferor
Yes, you can remove it if you don't care about the terminal output.Gerstein
Cool man! Thanks a lot for your help & with the explanation. It's always valued.Transferor
> /dev/null 2> /dev/null is better written as &>/dev/nullGirlfriend
E
4

I've tested this on a board which is configured via DHCP.

The assumption is that if a default gateway exists on a specific interface (in this case eth0), then this is due to the fact that the board has gotten assigned an IP (and thus the default gateway) by the DHCP server, which implies that networking is up and running.

My issue was that for me networking is considered to be up as soon as machines in the LAN/intranet can be accessed, even if there exists no internet connectivity (ie 8.8.8.8 or www.google.com is not accessible). I also don't want to ping specific IPs or domain names in the intranet because I don't want to make assumptions about the subnet or which devices will definitely be up and what their IP or domain name is.

while ! ip route | grep -oP 'default via .+ dev eth0'; do
  echo "interface not up, will try again in 1 second";
  sleep 1;
done
Enquire answered 19/10, 2018 at 15:54 Comment(0)
A
3

How would one go about adding this functionality in a script -- Geofferey

Following be part of how I'm going about solving a similar situation in a modular fashion...

await-ipv4-address.sh

#!/usr/bin/env bash


## Lists IP addresses for given interface name
## @returns {number|list}
## @param {string} _interface      - Name of interface to monitor for IP address(es)
## @param {number} _sleep_intervel - Number of seconds to sleep between checks
## @param {number} _loop_limit     - Max number of loops before function returns error code
## @author S0AndS0
## @copyright AGPL-3.0
## @exampe As an array
##     _addresses_list=($(await_ipv4_address 'eth0'))
##     printf 'Listening address: %s\n' "${_addresses_list[@]}"
##     #> Listening address: 192.168.0.2
##     #> Listening address: 192.168.0.4
## @example As a string
##     _addresses_string="$(await_ipv4_address 'eth0' '1' '3')"
##     printf 'Listening address(es): %s\n' "${_addresses_string}"
##     #> Listening address(es): 192.168.0.2 192.168.0.4
await_ipv4_address(){
    local _interface="${1:?# Parameter_Error: ${FUNCNAME[0]} not provided an interface}"
    local _sleep_interval="${2:-1}"
    local _loop_limit="${3:-10}"
    if [ "${_sleep_interval}" -lt '0' ] || [ "${_loop_limit}" -le '0' ]; then
        printf 'Parameter_Error: %s requires positive numbers for second and third parameters\n' "${FUNCNAME[0]}" >&2
        return 1
    fi

    local _loop_count='0'
    local -a _ipv4_addresses
    while true; do
        for _address in $({ ip addr show ${_interface} | awk '/inet /{print $2}'; } 2>/dev/null); do
            _ipv4_addresses+=("${_address}")
        done

        if [ "${#_ipv4_addresses[@]}" -gt '0' ]; then
            printf '%s\n' "${_ipv4_addresses[*]}"
            break
        elif [ "${_loop_count}" -gt "${_loop_limit}" ]; then
            break
        fi

        let _loop_count+=1
        sleep "${_sleep_interval}"
    done

    [[ "${#_ipv4_addresses[@]}" -gt '0' ]]; return "${?}"
}

Source for above are on GitHub bash-utilities/await-ipv4-address, check the ReadMe file for instructions on utilizing Git for updates and bug fixes.

To source the above function within current shell...

source "await-ipv4-address.sh"

... or within another script...

#!/usr/bin/env bash


## Enable sourcing via absolute path
__SOURCE__="${BASH_SOURCE[0]}"
while [[ -h "${__SOURCE__}" ]]; do
    __SOURCE__="$(find "${__SOURCE__}" -type l -ls | sed -n 's@^.* -> \(.*\)@\1@p')"
done
__DIR__="$(cd -P "$(dirname "${__SOURCE__}")" && pwd)"


source "${__DIR__}/modules/await-ipv4-address/await-ipv4-address.sh"


Would awk or grep be of use in a situation like this? -- Geofferey

Both may be used; though much like echoing I think greping is best done in the privacy of one's own shell... I prefer awk in public as there's a whole scripting language to facilitate feature creep, and printf because it's not as likely to have missing features on slimmed-down environments.

Here's how to awk an address regardless of IPv4 vs. IPv6 flavor....

    # ... trimmed for brevity
        for _address in $({ ip addr show ${_interface} | awk '/inet/{print $2}'; } 2>/dev/null); do
            # ... things that get done with an address
        done

... just a difference in space to get more data.


Something I can have be compatible regardless of interface name or type

Three different interface name examples and how to overwrite some of the default behavior

  1. Wait upwards of ten seconds for an IP on eth0
_ip_addresses_list=($(await_ipv4_address 'eth0'))
  1. Wait upwards of thirty seconds for an IP on tun0
_ip_addresses_list=($(await_ipv4_address 'tun0' '1' '29'))
  1. Wait upwards of a minuet for an IP on wlan0 while sleeping 3 seconds between checks
_ip_addresses_list=($(await_ipv4_address 'wlan0' '3' '19'))

Note, if await_ipv4_address gets board it'll return a non-zero status, so the following...

_ip_addresses_list=($(await_ipv4_address 'wlan0' '3' '19' || true))

... may be used if you've got error traps that get tripped by such things.


Then do stuff with the IP addresses once assigned...

for _ip_address in "${_ip_addresses_list[@]}"; do
    printf 'IP -> %s\n' "${_ip_address}"
done

Wait for network interface to do what? -- user207421

to be up, but more than up lol I need an active connection where I'm sure I can connect to internet -- Geofferey

The above will not test for an active connection to the greater Internet, only if an IP address has been assigned via the local network switch/AP or static settings; though, a local IP is a prerequisite... consider the above part of an answer that is script friendly as it's only designed to preform one thing well.

To reliably detect if connections to the rest of the world wide web are permitted check out dig and curl, because a successful ping to one's favorite DNS does not mean other protocols are allowed.


Could you explain each part of your script so I am not blindly using something without having an understanding of how it works? -- Geofferey

... Sure...

await_ipv4_address(){
    local _interface="${1:?# Parameter_Error: ${FUNCNAME[0]} not provided an interface}"
    local _sleep_interval="${2:-1}"
    local _loop_limit="${3:-10}"
    # ...
}
  • local assigns locally scoped variables, help local and help declare will show some useful documentation on more advanced usage

  • "${something:?Error message}" will print Error message if something is not assigned

  • "${another_thing:-1}" will default to 1 if another_thing is not assigned or assigned an null value

Hint, man --pager='less -p ^"PARAMETERS"' bash through till end of Special Parameters section as well as the man --pager='less -p "Parameter Expansion"' bash section may be helpful in finding more things that can be done with variables and stuff.


    if [ "${_sleep_interval}" -lt '0' ] || [ "${_loop_limit}" -le '0' ]; then
        printf 'Parameter_Error: %s requires positive numbers for second and third parameters\n' "${FUNCNAME[0]}" >&2
        return 1
    fi
  • throws errors if either _sleep_interval or _loop_count are not numbers because of less-than (-lt) and less-than or equal-to (-le) checks

  • throws error if either of if checks return true, the || chains multiple checks such that if the left side returns false it trips the right side for a check, where as && would only fire if the left side returned true

hint man operator will show directionality of various operators

  • printf 'something\n' >&2 writes something to standard error; where all well-behaved errors should be written so that logs can be made or output ignored

  • shows a level of paranoia about function inputs that may be excessive


    while true; do
        # ... stuff
    done
  • should be used with care because if state is not checked and updated correctly the loop will never exit.

        for _address in $({ ip addr show ${_interface} | awk '/inet /{print $2}'; } 2>/dev/null); do
            _ipv4_addresses+=("${_address}")
        done
  • the $({ command | parser; } 2>/dev/null) trick is something that I picked-up from around these parts

    • $(something) runs something within a sub-shell
    • { one_thing | another_thing; } is a compound command

    Hint, man --pager='less -p "Compound Commands"' bash should show relevant documentation

    • 2>/dev/null causes standard error to be written where no input returns
  • _preexisting_list+=("element") appends element to _preexisting_list by way of +=


        if [ "${#_ipv4_addresses[@]}" -gt '0' ]; then
            printf '%s\n' "${_ipv4_addresses[*]}"
            break
        elif [ "${_loop_count}" -gt "${_loop_limit}" ]; then
            break
        fi
  • if part checks if the number of elements within _ipv4_addresses are greater than 0 via the # sign, ${#_list_name[@]}

  • elif part checks if function should be board by now

In either case a break from the while loop is taken when logic is tripped.


        let _loop_count+=1
        sleep "${_sleep_interval}"
  • let _counter+=1 will add 1 to whatever previous value was in _counter and assign it to _counter

  • sleep causes loop to chill out for a number of seconds so other things can be contemplated by the device


        [[ "${#_ipv4_addresses[@]}" -gt '0' ]]; return "${?}"
  • Bash semicolons with testing brackets ([[ is_it_true ]]) instead of || or && causes return to return the status of if the number of IP addresses found where greater than 0 regardless of truthiness of test

If there's something questionable after all that feel free to post a comment so that the answer can be improved.

Arnulfo answered 12/7, 2019 at 2:11 Comment(0)
S
1

You can do the following

until ifconfig -l | grep ppp0 >/dev/null 2>&1; do :; done

Works on MacOS

Stumpy answered 18/3, 2022 at 9:13 Comment(0)
J
0

Based on @abd3lraouf's answer, I found out the following worked for debian:

until ifconfig -a | grep "can0" 2>&1; do :; done
Janis answered 28/3 at 15:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.