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...
#!/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 theReadMe
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 echo
ing I think grep
ing 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
- Wait upwards of ten seconds for an IP on
eth0
_ip_addresses_list=($(await_ipv4_address 'eth0'))
- Wait upwards of thirty seconds for an IP on
tun0
_ip_addresses_list=($(await_ipv4_address 'tun0' '1' '29'))
- 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
andhelp declare
will show some useful documentation on more advanced usage"${something:?Error message}"
will printError message
ifsomething
is not assigned"${another_thing:-1}"
will default to1
ifanother_thing
is not assigned or assigned an null value
Hint,
man --pager='less -p ^"PARAMETERS"' bash
through till end ofSpecial Parameters
section as well as theman --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
) checksthrows error if either of
if
checks returntrue
, the||
chains multiple checks such that if the left side returnsfalse
it trips the right side for a check, where as&&
would only fire if the left side returnedtrue
hint
man operator
will show directionality of various operators
printf 'something\n' >&2
writessomething
to standard error; where all well-behaved errors should be written so that logs can be made or output ignoredshows 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)
runssomething
within a sub-shell{ one_thing | another_thing; }
is a compound command
Hint,
man --pager='less -p "Compound Commands"' bash
should show relevant documentation2>/dev/null
causes standard error to be written where no input returns
_preexisting_list+=("element")
appendselement
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 than0
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 add1
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&&
causesreturn
to return the status of if the number of IP addresses found where greater than0
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.