Bash - Convert netmask in CIDR notation? - bash

Example:
I have this netmask: 255.255.255.0
Is there, in bash, a command or a simple script to convert my netmask in notation
/24?

Function using subnetcalc:
IPprefix_by_netmask() {
subnetcalc 1.1.1.1 "$1" -n | sed -n '/^Netw/{s#.*/ #/#p;q}'
}
In pure bash, (i.e. no external utils like sed or bc), convert IP to a long octal string and sum its bits:
IPprefix_by_netmask () {
c=0 x=0$( printf '%o' ${1//./ } )
while [ $x -gt 0 ]; do
let c+=$((x%2)) 'x>>=1'
done
echo /$c ; }
Output of IPprefix_by_netmask 255.255.255.0 (either function):
/24

Example Function for RHEL6/RHEL7:
IPprefix_by_netmask() {
#function returns prefix for given netmask in arg1
ipcalc -p 1.1.1.1 $1 | sed -n 's/^PREFIX=\(.*\)/\/\1/p'
}
The Result:
$ IPprefix_by_netmask 255.255.255.0
/24
In other Linux distributives ipcalc options may differ.
The same function without ipcalc, tested in Solaris and Linux:
IPprefix_by_netmask() {
#function returns prefix for given netmask in arg1
bits=0
for octet in $(echo $1| sed 's/\./ /g'); do
binbits=$(echo "obase=2; ibase=10; ${octet}"| bc | sed 's/0//g')
let bits+=${#binbits}
done
echo "/${bits}"
}

Solution based on awk
While GNU awk is not Bash, it’s installed by default in enough distributions that this may be helpful in the sense of the question:
awk -F. '{
split($0, octets)
for (i in octets) {
mask += 8 - log(2**8 - octets[i])/log(2);
}
print "/" mask
}' <<< 255.255.255.240
This prints:
/28

Based on Sasha's answer, this script works with dash (tested with Ubuntu 18.04):
IPprefix_by_netmask() {
#function returns prefix for given netmask in arg1
bits=0
for octet in $(echo $1| sed 's/\./ /g'); do
binbits=$(echo "obase=2; ibase=10; ${octet}"| bc | sed 's/0//g')
bits=$(expr $bits + ${#binbits})
done
echo "/${bits}"
}

Related

How to save to the var, only one from the output

Im writing a script that executes dig command on 2 domains, and after next cmd is host on output.
And always i will get for exmaple:
findUserServer=for r in $(dig +short $login.example.COM && dig +short $login.example.ORG); do host $r|awk '{print $NF}';done | awk -F "." '{print $1}';
1 output: >> asdf02 example
asdf02 - its a server name, its always same name starts "asdf".
Question: Have you any idea how to save to the variable only asdf02?
question+: asdf02 woudln't be always first, could be example asdf02
Should i do maybe a sed which looks on 4 first characters? If it's "asdf", then: [...]
Try not to pipe awk commands into each other and so:
for r in $(dig +short $login.example.COM && dig +short $login.example.ORG); do host $r;done | awk -F [.\ ] '/asdf02/ { print $10 }'
We use both a space and . as delimiters and then pattern match the output for the occurance of asdf02. If we find is, we print the address.
Run that through shellcheck.net ...
Try this.
findUserServer="$( for end in COM ORG; do
host $( dig +short $login.example.$end );
done | sed -n '/ asdf/{ s/^.* //; s/[.].*//; p; }' )"
This will run 2 digs and pipe the collective output through sed,
which will ignore lines that don't have asdf, and strip the matches clean for you.
Let me know if I missed details, because I don't have those exact values available.

Bash Replace Variable IP Address with Network

I have an IP address set in a variable that I'd like to convert into a network address.
This only works for a single digit:
echo '192.168.1.2' | sed 's/.$/0/' => 192.168.1.0
echo '192.168.1.22' | sed 's/.$/0/' => 192.168.1.20
echo '192.168.1.223' | sed 's/.$/0/' => 192.168.1.220
I need a method to return the same network value if the last digit(s) change, i.e:
myip="192.168.1.2" => "192.168.1.0"
myip="192.168.1.22" => "192.168.1.0"
myip="192.168.1.223" => "192.168.1.0"
How can I replace any IP address with it's network address like above?
Pure bash solution without external commands:
echo "${myip%.*}.0"
for example:
$ echo "$myip"
192.168.1.22
$ echo "${myip%.*}.0"
192.168.1.0
Using sed
echo '192.168.1.2' | sed 's/\.[^.]*$/.0/'
sed 's/\.[^.]*$/.0/' <<< 192.168.1.22 # echo + pipe is not needed here
Logic: Replace everything from last . till end with .0
Using awk
awk -F. '{$NF=0}1' OFS=. <<< 192.168.1.22
awk '{$NF=0}1' FS=. OFS=. <<< 192.168.1.22
Logic: Split string with . and set last field to 0.
pure bash:
{ IFS=. read a b c _; echo $a.$b.$c.0; } <<< 192.168.1.22
( IFS=.; read -a ip; ip[3]=0; echo "${ip[*]}"; ) <<< 192.168.1.22
Logic: Read 4 parts of the IP address in 4 variables. Print first 3 and a 0.
Or by using a bash array, if you don't want to clutter environment with too many variables.
You can do this with awk using:
pax> awk -F. '{print $1"."$2"."$3".0"}' <<<12.34.56.78
12.34.56.0
With sed, it's possible to just replace all the digits at the end:
pax sed 's/[0-9]*$/0/' <<<12.34.56.78
12.34.56.0
However, all of those result in an extra process being started up, not something you need to worry about for a few IP addresses but it will make a difference if you're converting many of them.
To do it within bash only (not requiring another process), you can use:
pax> ip=12.34.56.78
pax> echo ${ip%.[0-9]*}.0
12.34.56.0
It is very simple to do with pure bash:
myip="192.168.1.2 "; echo "$myip ==> ${myip%.*}.0"
myip="192.168.1.22 "; echo "$myip ==> ${myip%.*}.0"
myip="192.168.1.223"; echo "$myip ==> ${myip%.*}.0"
Results in:
192.168.1.2 ==> 192.168.1.0
192.168.1.22 ==> 192.168.1.0
192.168.1.223 ==> 192.168.1.0
However, that is assuming the network has a CDIR of 24 (192.168.1.2/24).
If that is not what you will always use, this idea will break.

How do I represent a newline character in bash inline string shell replacement?

I can modify a string like the following:
mod=${orig//[xyz]/_}
It will replace all occurances of [xyz] with _
But if I have a string such as eth0 eth1 eth2, how do I replace ' ' space with newline \n. The following does not work.
orig="eth0 eth1 eth2"
mod=${orig// /\n}
This is how I'm planning to use it:
VRRP_INTERFACE="${VRRP_INTERFACE:-ib0}"
VRRP_ADDITIONAL_INTERFACES="${VRRP_ADDITIONAL_INTERFACES// /\\n}"
cat << EOF > /etc/keepalived/keepalived.conf
vrrp_instance VI_1 {
interface ${VRRP_INTERFACE}
state BACKUP
virtual_router_id ${VRRP_ROUTER_ID}
priority ${VRRP_PRIORITY}
advert_int 1
# Monitor these as well
track_interface {
$VRRP_ADDITIONAL_INTERFACES
}
virtual_ipaddress {
${VRRP_VIP} dev ${VRRP_INTERFACE} label ${VRRP_INTERFACE}:1
}
EOF
I note that if I use
echo -e $VRRP_ADDITIONAL_INTERFACES it works when the string contains a "\n" in the string. i.e. replace with \n rather than \n.
But in the case of using cat << EOF > filename format it doesn't work.
Use $'\n':
$ orig="eth0 eth1 eth2"
$ mod=${orig// /$'\n'}
$ echo "$mod"
eth0
eth1
eth2
This approach puts actual newline characters into the string mod.
Using cat << EOF
Consider this shell script which uses the same substitution:
orig="eth0 eth1 eth2"
mod=${orig// /$'\n'}
cat << EOF > test.txt
track_interface {
$mod
}
EOF
cat test.txt
When run, this is the output:
$ bash script.sh
track_interface {
eth0
eth1
eth2
}
Note that this approach requires bash. Thus, on debian-like systems (for which sh is dash), the following will not work:
$ sh script.sh
matt.sh: 2: script.sh: Bad substitution
I figured out a method. It's not as pretty but works.
Assign the cat to a variable FILE.
Then echo -e "$FILE" > filename
i.e.
VRRP_INTERFACE="${VRRP_INTERFACE:-ib0}"
VRRP_ADDITIONAL_INTERFACES="${VRRP_ADDITIONAL_INTERFACES// /'\n'}"
FILE=$(cat << EOF
vrrp_instance VI_1 {
interface ${VRRP_INTERFACE}
state BACKUP
virtual_router_id ${VRRP_ROUTER_ID}
priority ${VRRP_PRIORITY}
advert_int 1
# Monitor these as well
track_interface {
$VRRP_ADDITIONAL_INTERFACES
}
virtual_ipaddress {
${VRRP_VIP} dev ${VRRP_INTERFACE} label ${VRRP_INTERFACE}:1
}
EOF
)
echo -e "$FILE" > /etc/keepalived/keepalived.conf
#John1024 solution, works well
orig="eth0 eth1 eth2"
mod=${orig// /$'\n'}
cat << EOF > test.txt
track_interface {
$mod
}
EOF

Reverse order of a string

I want to "reverse" the order of the four octets (bytes) that make up an ip address.
Suppose I have this ip:
202.168.56.32
I need to convert into:
32.56.168.202
and then ultimately remove the first octet in the reversed ip. Final result:
56.168.202
My attempts:
echo 202.168.56.32 | rev
But it's returning :
23.65.861.202
This should do the trick:
echo 202.168.56.32|awk -F. '{print $3"."$2"."$1}'
You could also do it with bash arrays:
ip=202.168.56.32
parts=(${ip//./ })
echo ${parts[2]}.${parts[1]}.${parts[0]}
Or you could use sed.
echo 202.168.56.32 | sed -e 's/\([0-9]*\)\.\([0-9]*\)\.\([0-9]*\)\.\([0-9]*\)/\4.\3.\2.\1/g'

currency parsing and conversion using shell commands

I'm looking for a shell one-liner that will parse the following example currency string PHP10000 into $245. I need to parse the number from the string, multiply it with a preset conversion factor then add a "$" prefix to the result.
So far, what I have is only this:
echo PHP10000 | sed -e 's/PHP//'
which gives 10000 as result.
Now, I'm stuck on how to do multiplication on that result.
I'm thinking awk could also give a solution to this but I'm a beginner at shell commands.
Update:
I tried:
echo PHP10000 | expr `sed -e 's/PHP//'` \* 2
and the multiplication works properly only on whole numbers. I can't use floating point numbers as it gives me this error: expr: not a decimal number: '2.1'.
value=PHP10000
factor=40.82
printf -v converted '$%.2f' "$(bc <<< "${value#PHP} / $factor")"
echo $converted # => $244.98
the ${value#PHP} part is parameter expansion that removes the PHP string from the front of the $value string
the <<< part is a bash here-string, so you're passing the formula to the bc program
bash does not do floating point arithmetic, so call bc to perform the calculation
printf -v varname is the equivalent of other languages varname = sprintf(...)
One way:
echo "PHP10000" | awk -F "PHP" '{ printf "$%d\n", $2 * .0245 }'
Results:
$245
Or to print to two decimal places:
echo "PHP10000" | awk -F "PHP" '{ printf "$%.2f\n", $2 * .0245 }'
Results:
$245.00
EDIT:
Bash doesn't support floating point operations. Use bc instead:
echo "PHP10000" | sed 's/PHP\([0-9]\+\)/echo "scale=2; \1*.0245\/1" | bc/e'
Results:
245.00
Something like:
echo PHP10000 | awk '/PHP/ { printf "$%.0f\n", .0245 * substr($1,4) }'
It can be easily extended to a multi-currency version that converts into one currency (known as quote currency), e.g.:
awk '
BEGIN {
rates["PHPUSD"]=.01
rates["GBPUSD"]=1.58
}
/[A-Z]{3}[0-9.]+/ {
pair=substr($1,1,3) "USD"
amount=substr($1,4)
print "USD" amount * rates[pair]
}
' <<EOF
PHP100
GBP100
EOF
Outputs:
USD1
USD158
Yet another alternative:
$ echo "PHP10000" | awk 'sub(/PHP/,""){ print "$" $0 * .0245 }'
$245

Resources