I have a file that has a bunch of hostnames correlated with IPs that looks like this:


I want it to look like this:


How can I replace the . (dots) from the first column with - (hyphen) in order to facilitate a sort by the second column? I was thinking of using sed to replace dots until the first space, or replacing every dot but the last three, but I'm having trouble understanding regex and sed. I can perform simple replaces but this is way over my head!

This is part of a larger script that I have been writing in bash. I am stuck at this part.

7 Answers 7


You can use AWK

awk '{gsub(/-/,".",$1);print}' infile


awk splits a line on whitespace by default. Thus, the first column of the line ($1 in awk-ese) will be the one you want to perform the substitutions on. For this purpose, you can use:


to perform the required substitution.

Note that gsub is supported only for gawk and nawk but on many modern distros awk is a softlink to gawk.

  • 1
    +1 Beat me to it. I think an explanation would really benefit the asker and future readers as well.
    – Joseph R.
    Commented Jan 28, 2014 at 15:33
  • 1
    @JosephR. Sorry I'm not good at explanation but I have tried and updated.. Commented Jan 28, 2014 at 15:41
  • 2
    The POSIX spec for awk is based on nawk, so all modern awk implementations should have gsub. On Solaris, you may need /usr/xpg4/bin/awk or nawk. Commented Jan 28, 2014 at 15:46
  • @RahulPatil If you don't mind, I added a few lines that I think would help others.
    – Joseph R.
    Commented Jan 28, 2014 at 15:47
  • @JosephR thanks.., it's seems perfect now.. :) Commented Jan 28, 2014 at 15:48

If you need to do the substitutions on the first field, best is to use Rahul's awk solution but beware it may affect the spacing (fields are rewritten with a single space in between them).

You can avoid it by writing it instead:

perl -pe 's|\S+|$&=~tr/./-/r|e' file

The -p flag means "read the input file line by line and print each line after applying the script given by -e". Then, substitute (s|pattern|replacement|) the first sequence of non-space characters (\S+) with the matched pattern ($&) after substituting all . with -. The trick is to use s|||e where the e operator will evaluate an expression as a replacement. So, you can have one replacement (tr/./-/) applied to the match ($&) of the previous one (s|||e).

If you need to substitute every . with a - except the last 3 last ones, with GNU sed and assuming you have a rev command:

rev file | sed 's/\./-/4g' | rev
  • 1
    Note that the Perl solution assumes version 5.14 or higher (for the /r to work).
    – Joseph R.
    Commented Jan 28, 2014 at 23:33

Sed isn't the easiest tool for the job — see other answers for better tools — but it can be done.

To replace . by - only up to the first space, use s in a loop.

sed -e '
  : a                     # Label "a" for the branching command
  s/^\([^ .]*\)\./\1-/    # If there is a "." before the first space, replace it by "-"
  t a                     # If the s command matched, branch to a

(Note that some sed implementations do not support comments on the same line. GNU sed does.)

To instead perform the replacement up to the last space:

sed -e '
  : a                     # Label "a" for the branching command
  s/\.\(.* \)/-\1/        # If there is a "." before the last space, replace it by "-"
  t a                     # If the s command matched, branch to a

Another technique makes use of sed's hold space. Save the bit you don't want to modify into the hold space, do your work, then recall the hold space. Here, I split the line at the last space and replace dots by dashes in the first part.

sed -e '
  h           # Save the current line to the hold space
  s/.* / /    # Remove everything up to the last space
  x           # Swap the work space with the hold space
  s/[^ ]*$//  # Remove everything after the last space
  y/./-/      # Replace all "." by "-"
  G           # Append the content of the hold to the work space
  s/\n//      # Remove the newline introduced by G

Since Rahul gave you the canonical answer for your use case, I thought I'd take a stab at answering the titular problem: substituting all but the last x occurrences of a regex:

perl -pe '
    $count = tr{.}{.}; # Count '.' on the current line
    $x = 3;
    next LINE if $count <= $x;
    while(s{\.}{-}){   # Substitute one '.' with a '-'
        last if ++$i == $count - $x # Quit the loop before the last x substitutions
$i = 0
' your_file

The above code (tested) does not assume that you have space-separated fields. It will replace all dots on a line with dashes except the last 3 dots. Replace the 3 in the code to your liking.


You can use many different tools for this. Rahul Patil already gave you a gawk one so here are a few others:

  • perl

    perl -lane  '$F[0]=~s/\./-/g; print "@F"' file

    The -a switch causes perl to automatically split input lines on whitespace and save the resulting fields into the array @F. The first field, therefore, will be $F[0] so we replace (s///) all occurrences of . with - in the first field and then print the entire array.

  • shell

     while read -r a b; do printf "%s %s\n" "${a//./-}" "$b"; done < file 

    Here, the while loop reads the file and automatically splits on whitespace.This creates two fields, $first and $rest. The construct ${first//pattern/replacement} replaces all occurrences of pattern with replacement.

  • +1 While perlrun(1) will tell you that -a is "autosplit mode", I prefer to think of it as "awk mode" :D
    – Joseph R.
    Commented Jan 28, 2014 at 15:50

I believe this is a bit easier to read than a large nasty regex. Basically I just split the line into two fields at the whitespace and use sed on the first part.

while read -r host ip; do
    echo "$(sed 's/\./-/g' <<< "$host") $ip"
done < input_file

Depending on your shell you could also use ${host//./-} instead of the sed command.

sed 's/\./-/' <file name>

Without using g at the end of the command you can do this… This will simply replace the 1st occurance of the pattern

You must log in to answer this question.

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