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?

3
  • 1
    Where are those strings? In a bash variable? In a text file one per line? Dec 21, 2015 at 16:17
  • It is in a variable. I am sorry was forgot to mention it.
    – Ari
    Dec 21, 2015 at 16:26
  • 3
    If those are IP addresses, then 444 and 888 are invalid octet values. Dec 21, 2015 at 22:47

5 Answers 5

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.

3
  • What is ${var%.*} ?
    – voices
    Dec 22, 2015 at 1:34
  • @tjt263, at the command line type man bash and press enter, then type /suffix pattern and press enter. :)
    – Wildcard
    Dec 22, 2015 at 6:41
  • Added some quotes following your post: new_var="${var%.*}.0" ... nJoy!.
    – user79743
    Dec 22, 2015 at 12:03
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.

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.

1
  • Great answer!!!
    – k.Cyborg
    Jun 2, 2022 at 7:18
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
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.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .