15

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?

Wildcard
  • 36,499
Ari
  • 253

5 Answers5

29

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.

9

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.

Kira
  • 4,807
6

General case for applying a netmask to an IP address:

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 integer
  • i2d() does the opposite - converts a simple integer to a dotted decimal
  • ipmask() 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.

5

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
terdon
  • 242,166
0

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.

Aaron
  • 281