I have the following pattern in a string (an IP address):
123.444.888.235
I want to replace the last number after the dot with 0
, so it becomes:
123.444.888.0
How could I do it in bash
or another shell script language?
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. It only takes a minute to sign up.
Sign up to join this communityI have the following pattern in a string (an IP address):
123.444.888.235
I want to replace the last number after the dot with 0
, so it becomes:
123.444.888.0
How could I do it in bash
or another shell script language?
In any POSIX shell:
var=123.444.888.235
new_var="${var%.*}.0"
${var%pattern}
is an operator introduced by ksh
in the 80s, standardized by POSIX for the standard sh
language and now implemented by all shells that interpret that language, including bash
.
${var%pattern}
expands to the content of $var
stripped of the shortest string that matches pattern off the end of it (or to the same as $var
if that pattern doesn't match). So ${var%.*}
(where .*
is a pattern that means dot followed by any number of characters) expands to $var
without the right-most .
and what follows it. By contrast, ${var%%.*}
where the longest string that matches the pattern is stripped would expand to $var
without the left-most .
and what follows it.
man bash
and press enter, then type /suffix pattern
and press enter. :)
new_var="${var%.*}.0"
... nJoy!.
This should work.
echo 123.444.888.235 | sed 's/\([0-9]*\.[0-9]*\.[0-9]*\.\)[0-9]*/\10/'
Note that the last field of sed
substitution, \10
, is the first matched pattern (\1
), concatenated with a literal zero.
Given that your input is an IP address, and that you are replacing the last octet of that address with .0
, then I assume what you are really trying to achieve is to compute the network portion of the IP address, using the 255.255.255.0
netmask.
Simply replacing octets with zeroes is OK if your netmask length is divisible by 8, but this is not the general case. If you ever need to perform this operation for any valid (subnet) netmask, then you can do something like this:
function d2i() {
echo $(( 0x$( printf "%02x" ${1//./ } ) ))
}
function i2d() {
h=$( printf "%08X" "$1" )
echo $(( 0x${h:0:2} )).$(( 0x${h:2:2} )).$(( 0x${h:4:2} )).$(( 0x${h:6:2} ))
}
function ipmask() {
i2d $(( $( d2i $1 ) & $( d2i $2 ) ))
}
ipmask 123.44.88.235 255.255.255.0 # outputs 123.44.88.0
ipmask 123.44.88.235 255.255.255.240 # outputs 123.44.88.224
This defines 3 functions:
d2i()
converts the dotted-decimal form of an IP address (or mask) to a simple integeri2d()
does the opposite - converts a simple integer to a dotted decimalipmask()
simply computes a bitwise AND of an address and a netmask to give the network portion of an address. Bash expects the operands of &
to be integers.The two calls to ipmask
show how the network may be calculated from an IP address for two different masks.
Note as stated in the question, 123.444.888.235
is an invalid IP address. I have used 123.44.88.235
instead for these examples.
A few ways (these all assume that you want to change the last set of numbers in the string):
$ echo 123.444.888.235 | awk -F'.' -vOFS='.' '{$NF=0}1;'
123.444.888.0
Here, -F'.'
tells awk
to use .
as the input field separator and -vOFS='.'
to use it as the output field separator. Then, we simply set the last field ($NF
) to 0 and print the line (1;
is awk
shorthand for "print the current line").
$ echo 123.444.888.235 | perl -pe 's/\d+$/0/'
123.444.888.0
The -p
tells perl
to print each input line after applying the script given by -e
. The script itself is just a simple substitution operator which will replace one or more numbers at the end of the line with 0
.
$ echo 123.444.888.235 | sed 's/[0-9]*$/0/'
123.444.888.0
The same idea in sed
, using [0-9]
instead of \d
.
If your string is in a variable and you use a shell that supports here strings (such as bash
or zsh
for example), you can change the above to:
awk -F'.' -vOFS='.' '{$NF=0}1;' <<<$var
perl -pe 's/\d+$/0/' <<<$var
sed 's/[0-9]*$/0/' <<<$var
An alternative sed
answer :
sed -r 's/(.*\.).*/\10/'
It groups everything up to the last dot and replaces the full line with the content of the group followed by 0.
I use -r to avoid having to escape parentheses.