Wait for Network Interface Before Executing Command Wait for Network Interface Before Executing Command shell shell

Wait for Network Interface Before Executing Command


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.


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


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.4await_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.