Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

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

x-cluster-front-1 192.168.1.2
x-cluster-front-2 192.158.1.10
y-cluster-back-1 10.1.11.99
y-cluster-back-2 10.1.157.38
int.test.example.com 59.2.86.3
super.awesome.machine 123.234.15.6

And i want it to look like this:

x-cluster-front-1 192.168.1.2
x-cluster-front-2 192.158.1.10
y-cluster-back-1 10.1.11.99
y-cluster-back-2 10.1.157.38
int-test-example-com 59.2.86.3
super-awesome-machine 123.234.15.6

My question is: How can i replace the . (dots) from the first column with - (line) in order to facilitate a sort by the second column. I was thinking of using sed to replace dots until 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 :D.

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

share|improve this question
add comment

6 Answers

You can use AWK

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

Explanation

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:

 gsub(regex,replacement,string)

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.

share|improve this answer
1  
+1 Beat me to it. I think an explanation would really benefit the asker and future readers as well. –  Joseph R. Jan 28 at 15:33
1  
@JosephR. Sorry I'm not good at explanation but I have tried and updated.. –  Rahul Patil Jan 28 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. –  Stephane Chazelas Jan 28 at 15:46
    
@RahulPatil If you don't mind, I added a few lines that I think would help others. –  Joseph R. Jan 28 at 15:47
    
@JosephR thanks.., it's seems perfect now.. :) –  Rahul Patil Jan 28 at 15:48
add comment

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
share|improve this answer
1  
Note that the Perl solution assumes version 5.14 or higher (for the /r to work). –  Joseph R. Jan 28 at 23:33
add comment

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.

share|improve this answer
    
This doesn't work. You need to reset $i otherwise it just keeps being incremented and you end up replacing all .. Also, it will replace the first . even if there are fewer than $count matches. Did you run this on the OP's example? –  terdon Jan 28 at 16:25
    
@terdon Thanks. I tested with only one line and forgot to add provisions for other cases. Should be fixed now. –  Joseph R. Jan 28 at 16:30
add comment

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
'
share|improve this answer
add comment

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.

share|improve this answer
    
+1 While perlrun(1) will tell you that -a is "autosplit mode", I prefer to think of it as "awk mode" :D –  Joseph R. Jan 28 at 15:50
add comment

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.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.