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.
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.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
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 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.
You can do the following
until ifconfig -l | grep ppp0 >/dev/null 2>&1; do :; done
Works on MacOS
Related
I have an application where I want the user to be able to enter ip addresses that are saved to a conf file. The addresses need to be checked to ensure they are valid ip addresses (xxx.xxx.xxx.xxx)
Given that this is a user set persistent value running on a user application (ie. not root), the conf file must reside in a user folder. I have chosen the user home directory (Raspbian).
The conf file test sample looks like this:
interface=eth0
ip_address=172.30.21.40
routers=172.30.21.1
domain_name_server_1=199.85.126.30
damaim_name_server_2=8.8.8.8
If the user saves a valid ip_address, I want to read and store this in a variable .
If the user saves an invalid ip_address, then I want to read and discard the ip address and return an empty string.
I have looked at range of options to do this.
I looked at using source, but I found this requires the conf to be executable. That would add the risk of a user injecting executable code into the conf file.
I think I should be able to read, check and store the ip_address value in a one line sed command, but I just can't get it to work.
The test script is:
!/bin/bash
conf_file='/home/user/ip.conf'
v1="$(sed -n 's/\b(?:ip_address=)(((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))\b/\1/p' $conf_file)"
echo "The ip address is : $v1"
exit
To break this down into parts:
\b(?:ip_address=) # match the string "ip_address=" starting with a word separator \b
(((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))
^ ^
# This section checks the format and number range of the ip address. This is made up of three
# groups that are all contained with a set of brackets (marked with ^) to create a group 1 with
# the whole ip address. This is what I want to capture. This ends with a word separator \b
/\1/p # This is the substitution section where I specify group 1 and print to save to $v1.
When I run this command I get the error
sed: -e expression #1, char 110: invalid reference \1 on `s' command's RHS
When I enter:
\b(?:ip_address=)(((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))\b
into the online regex tester it works without error. It identifies the full ip address as group 1.
The sed command doesn't seem to recognise the back reference \1 and I can't figure out what I am doing wrong.
Edit
I tried a simple command:
v1="$(sed -n -E 's/^\s*(interface=)(.*)\b/\2/p' $conf_file)"
This only worked correctly with the -E option added. This is based on an answer found here. I can't find any documentation on -E but it appears to enable extended regular expressions.
Too much. Big problems are sum of small problems - just take one little problem one at a time.
# Filter lines with ip_address. Allow leading spaces.
if ! ip_address=$(grep '^[[:space:]]*ip_address=' "$conf_file")l then
echo "ERROR: no line ip_Address found in config file" >&2
exit 2
fi
# dissallow two ip_address= lines
if [[ "$(printf "%s\n" "$ip_address" | wc -l)" -gt 1 ]]; then
echo "ERROR: There are two lines with ip_address in config file!" >&2
exit 2
fi
# remove the string before `=`
ip_address=${ip_address##*=}
# check if it's a valid address
re='^(0*(1?[0-9]{1,2}|2([0-4][0-9]|5[0-5]))\.){3}'
re+='0*(1?[0-9]{1,2}|2([0-4][0-9]|5[0-5]))$'
if [[ ! $ip_address =~ $re ]]; then
echo "ERROR: ip_Address option is not valid ip address" >&2
exit 2
fi
echo "found ip_Address=$ip_address"
But sure, you can do it all in GNU sed, including error handling:
if ! ip_address=$(sed -n '
# if its the last line i should have ip_address in hold space
${
x
# if I dont, that means error
/^$/{
s/.*/ERROR: no ip_Address found in the file/
p
q 1
}
# print the ip address
p
}
# remove lines that are not ip_Addresses
/^[[:space:]]*ip_address=/!{d;b}
# remove ip_address= strnig
s///
# if there is something in hold space, means we already found ip_address
x
/^$/!{
s/.*/ERROR: two lines with ipaddress found/
p
q 1
}
x
# check if the rest is a valid ip addresss
/^\(0*\(1\?[0-9]\{1,2\}\|2\([0-4][0-9]\|5[0-5]\)\)\.\)\{3\}0*\(1\?[0-9]\{1,2\}\|2\([0-4][0-9]\|5[0-5]\)\)$/!{
s/.*/ERROR: Invalid ip address: &/
p
q 1
}
# hold the valid ip_address in hold space
h
' "$conf_file"); then
echo "$ip_address" >&2
exit 2
fi
echo "Found ip_address=$ip_address"
I believe your idea was to do it just like:
sed -n -E 's/^ip_address=(((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))$/\1/p' "$conf_file"
which would be "good enough", but will be silent if user makes a mistake.
Thanks to assistance in the comments, the problem was found to be the ?: term in the regex. sed couldn't process that. Here is a demo script of the solution I was looking for:
#!/bin/bash
conf_file='/home/user/ip.conf'
v1=$(sed -n -E 's/^\s*(ip_address=)(((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))\s(.*)$/\2/p' $conf_file)
echo "The ip address is : $v1"
v2="$(sed -n -E 's/^\s*(interface=)(.*)\s(.*)/\2/p' $conf_file)"
echo "The interface is : $v2"
v3=$(sed -n -E 's/^\s*(routers=)(((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))\b(.*)/\2/p' $conf_file)
echo "The router ip is : $v3"
exit 0
The test config file, which intentionally includes errors, is:
interface=eth0 #comment
ip_address=172.30.21.40 # comment
ip_address=123.30.21.40 comment
ip_address=1234.123.30.21.40
ip_address=ab3.dd30.21.40
routers=172.30.21.1 172.123.456.234
domain_name_server_1=199.85.126.30
damaim_name_server_2=8.8.8.8
The output is:
The ip address is : 172.30.21.40
123.30.21.40
The interface is : eth0
The router ip is : 172.30.21.1
The error detection isn't perfect (it doesn't gracefully handle duplicate lines), and for my application it doesn't need to be. It is good enough. In my application, users will not have direct access to the command line or their home directory so this script is intended to be a second line of defense against bad input. Your requirements might vary.
This one line of code completes a number of tasks.
open file,
searches for a option name,
reads the option value,
checks the option value format/content against the regex filter,
ignores other text and comments on the same line,
returns the option value if valid, or "" if not and,
saves to a variable.
If the entry is missing or invalid, the return value will be "". In that case, a default value will be used (code not shown).
Getting it to work took way too much time but I learnt a lot about regex and sed. Now that it works, it is easy to adapt to read other than ip addresses, as shown in the test files.
i have a lot of virtual servers with static ip-addresses, and with A-records for them. I should use associative array for installing of my CMS. But i have idea. Maybe i should use dig, or other dnsutil for that ?
So, what i start write:
start=test
dns=com
for i in "${start}" {1..20}."$dns"; do
echo $i >> "/tmp/temp"
done
for ns in `cat /tmp/temp`; do
if [[ `dig +short $ns=="192.168.110.1"` ]]; then
dig +short $ns
fi
done
But with my second loop something wrong. Can you help me ?
I should generate a list with my domains, like test1.com, test2.com ...
And after that i should get ip address. The next step will comparing with my system ip and if i have ip 192.168.110.1, i should get my domain name, like test2.com. It does not work, i broke my head, but i have no idea, how to do this. Please help, if it possible.
The immediate error is that [[ `dig +short $ns=="192.168.110.1"` ]] simply checks whether the output from dig is a nonempty string (which it isn't, because the string you pass in as a query isn't a valid one). The superficial fix is
if [[ `dig +short "$ns"` == "192.168.110.1" ]]; then ...
where the spaces around the equals operator are significant, and of course, the comparison should not be passed to dig as an argument; but I would refactor your script a whole lot more. It's not entirely clear what exactly you want the script to do, but something like this?
#!/bin/bash
start=test
dns=com
for i in {1..20}; do
host="$start.$i.$dns"
ip=$(dig +short "$host")
if [[ "$ip" == "192.168.110.1" ]]; then
# I'm guessing you want to perform a reverse lookup on the IP address here?
dig +short "$ip"
fi
done
Mainly I want to detect if DNS is configured properly on a machine by using nslookup. Sadly it seems that nslookup still returns success error codes when it fails to lookup an entry. Also the fact that different queries could return multiple results makes it harder to test it.
So I want to write a bash snippet that returns success if the dns entry resolved successfully. I don't care if I get multiple results.
Example nslookup -type=srv _ldap._tcp.DOMAIN.COM
The correct solution would be to use dig and test if there is any text with the short option:
[ "$(dig +short -t srv _ldap._tcp.example.com.)" ] && echo "got answer"
Agree the fact, nslookup, returns 0 for both successful and failing DNS look-ups. You can achieve what you are trying to do, but post-processing the output of the command.
You can put up a dnsLookup.sh script with something like
#!/bin/bash
# Checking for the resolved IP address from the end of the command output. Refer
# the normal command output of nslookup to understand why.
resolvedIP=$(nslookup "$1" | awk -F':' '/^Address: / { matched = 1 } matched { print $2}' | xargs)
# Deciding the lookup status by checking the variable has a valid IP string
[[ -z "$resolvedIP" ]] && echo "$1" lookup failure || echo "$1" resolved to "$resolvedIP"
Running for some sample URL's
dudeOnMac:~$ ./dnsLookup.sh www.google.com
www.google.com resolved to 206.78.111.12
dudeOnMac:~$ ./dnsLookup.sh www.googlejunkaddress.com
www.googlejunkaddress.com lookup failure
The trick is to use host | grep commands instead of nslookup because this one is less verbose, making it much easier to parse with grep.
Here is a command that fails if the DNS resolution fails:
host -t srv _ldap._tcp.EXAMPLE.COM | grep "has SRV record" >/dev/null || {
echo "FATAL: Unable to locate ldap servers, probably you are not on intranet or your DNS servers are broken."
exit 2
}
Note: As you can see my example it specific to SRV queries but you can easily adapt change the parameter and the grep filter to make it work with others.
I have the below for loop
for ip in 10.11.{32..47}.{0..255}
do
echo "<ip>${ip}</ip>"
done
I want to exclude this iprange: 10.11.{32..35}.{39..61} from the above for loop. This ip range is a subset of the above one. Is there a way to do that?
I tried this, this doesn't work:
abc=10.11.{34..37}.{39..61}
for ip in 10.11.{32..47}.{0..255}
do
if [[ $ip == $abc ]]
then
echo "not_defined"
else
echo "<ip>${ip}</ip>"
fi
done
Try this:
for ip in 10.11.{32..47}.{0..255}
do
echo 10.11.{32..35}.{39..61} | grep -q "\<$ip\>" && continue
echo "<ip>${ip}</ip>"
done
This of course is a simple solution which still loops through the complete set and throws away some unwanted elements. As your comment suggests, this may produce unnecessary delays during the parts which are skipped. To avoid these, you can generate the values in parallel to the processing like this:
for ip in 10.11.{32..47}.{0..255}
do
echo 10.11.{32..35}.{39..61} | grep -q "\<$ip\>" && continue
echo "${ip}"
done | while read ip
do
process "$ip"
done
If the process "$ip" is taking at least a minimal amount of time, then the time for the generation of the values will most likely not fall into account anymore.
If you want to skip the values completely, you also can use a more complex term for your IPs (but then it will not be clear anymore how this code derived from the spec you gave in your question, so I better comment it thoroughly):
# ranges below result in
# 10.11.{32..47}.{0..255} without 10.11.{32..35}.{39..61}:
for ip in 10.11.{32..35}.{{0..38},{62..255}} 10.11.{36..47}.{0..255}
do
echo "${ip}"
done
Try this:
printf "%s\n" 10.11.{32..47}.{0..255} 10.11.{32..35}.{39..61} | sort | uniq -u | while read ip; do echo $ip; done
Currently I'm creating a little script in BASH that will ask the user to fill in a new IP-address, subnet and gateway. That input goes to a file which will be writen to /etc/network/interface..
I got the script working, but it isn't bullet proof.
When the user input, is not a number but an alphabetic character, It returns and the user needs to fill again a number.
When the user still uses a alphabetic character, the script continues, even though giving a error.
My code:
echo "Fill in your IP"
read NEW_IP
oldIFS=$IFS
IFS=.
set -- $NEW_IP
while [ $# -ne "4" ]; do
echo "Must have 4 parts"
read NEW_IP
set -- $NEW_IP
done
for oct in $1 $2 $3 $4; do
echo $oct | egrep "^[0-9]+$" >/dev/null 2>&1
while [ "$?" -ne "0" ]; do
echo "$oct is not numeric. fill your IP in the correct format;"
read NEW_IP
set -- $NEW_IP
done
I'm new with bash, above I didn't make it by my self. I've found this script on the internet. The while and do, I made that by my self as a loop.
Everytime the user fills in a wrong number It must return untill the user filled in a correct format.
The real problem lies at the second half of the code. When I fill in a wrong IP like 10.10.10.a, I get the error like I want and I have to fill in for the second time my IP.
When I type 10.41.12.r, an error occured but this time, not complaining about the r at the end, but still complaining about the a which I inserted at the first. 10.41.12 will be checkt, but that last character is different.
I can imagine that everything will be stored in memory, but how do I clear that?
unset or $oct=
won't work
Hope that someone can help me with this. It's my first time programming, and this is giving me a headache
thanks.
Dave
for does not evaluate the condition several times. It just runs 4 times, setting $oct to $1 .. $4. If you try several times, your input will be accepted, even if not numeric. Moreover, you should check "Must have 4 parts" again after getting the input in the loop.
BTW, you should check that each $oct <= 255.
Duplicated code is often bad. Better read the input at one place and go through the same tests everytime.
prompt="Fill in your IP"
while
echo $prompt
read NEW_IP
IFS=.
set -- $NEW_IP
IFS=
if [ $# -ne 4 ]
then
prompt="Must have 4 parts"
continue
fi
for oct
do if [[ ! "$oct" =~ ^[0-9]+$ ]]
then
prompt="$oct is not numeric. fill your IP in the correct format;"
continue 2
fi
# You might add more checks here.
done
do break
done