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 like this

...
1562 first part
1563 H     col3 H col4
1564 H     col3 H col4
...
3241 H     col3 H col4
3242 third part
...

I want to replace only the first H in every line to H#, with # is its number of appearance. The output should be:

...
1562 first part
1563 H1    col3 H col4
1564 H2    col3 H col4
...
3241 H1652 col3 H col4
3242 third part
...

So far, I've tried:

max=`grep -c ' H ' b`
while [[ "$i" -le $max ]];do
  grep -m $i ' H ' b|tail -n1|sed "s/H/H$i/1"
  let i=i+1
done

This code is slow, it needs to read every line to replace and can't add the first part and third part of the file. Is there any better way to do this? Maybe awk? Thank you.

share|improve this question

4 Answers 4

up vote 7 down vote accepted

You can for example use this:

$ awk '/H/{sub("H", "H"++v)}1' file
1562 first part
1563 H1     col3 H col4
1564 H2     col3 H col4

3241 H3     col3 H col4
3242 third part
...

This looks for those lines containing H and replaces that H with H together with a variable we keep incrementing. Note you could use gsub() instead of sub() if you wanted to perform this change in all the matched patterns instead in just one.

The final 1 is a true condition, hence it performs the default awk action: {print $0}, that is, prints the full line.

share|improve this answer

Try this:

  awk 'BEGIN { hNum = 1; } { if ($2 == "H") { $2 = "H" hNum; hNum++; } print $0; }' yourFile > outFile

It run awk using space as separator, so $2 is the second token of each line and if $2 is equal to "H", then replace it with "H" followed by number started from 1. Finally print out line.

share|improve this answer
    
this works, however it makes the line unsorted. –  Ooker Aug 18 at 16:45

With perl:

perl -pe 's/\bH\b\K/++$i/e' file

You can replace -pe with -pi.back -e for in-place editing with the original saved as file.back or -pi -e for no backup.

share|improve this answer
    
What's the use of e modifier? –  Avinash Raj Aug 18 at 14:48
    
@AvinashRaj e to evaluate the substitution part as perl code. –  Stéphane Chazelas Aug 18 at 16:15
{   nl -bpH -w1 |
    sed 's/^\([0-9]*\)[ \t]*\([^H]*.\)/\2\1/'
} <<\DATA
...
1562 first part 
1563 H     col3 H col4
1564 H     col3 H col4
...
3241 H     col3 H col4
3242 third part
DATA

OUTPUT

...
1562 first part 
1563 H1     col3 H col4
1564 H2     col3 H col4
...
3241 H3     col3 H col4
3242 third part

That's the fastest way I can imagine it would be done - especially with a very large file. nl will number only lines containing the string H and insert that number at the head of the line followed by a <tab> character. It indents all other lines with a few spaces.

sed is passed nl's output over the |pipe. sed then replaces the following sequence:

  • 0 or more digits occurring at the beginning of the line (referenced as \1)
  • 0 or more <tab> or <space> characters
  • 0 or more characters that are not H then one character (referenced as \2)

...with \2\1.

So lines not containing an H get this treatment:

^''   .*.$ = ^.*.''$

And those that do get this one:

^(digit)*<tab>(not H)*H.*$ = ^(not H)*H(digit)*.*$

...where '' is an empty string.

For maximum portability you should replace the \t in [ \t] with a literal <tab> character.

share|improve this answer

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.