I got a file alphabet where a has multiple occurrence in a line.

$ cat alphabet
a b c d e f g 
h i j k a a l
m n a p q r a
s t u v w a x
y z a k l q z


$ cat alphabet | grep -o a | wc -l

Now how can I replace only first 3 occurrence of a with Z so that my file looks like as follows

Z b c d e f g 
h i j k Z Z l
m n a p q r a
s t u v w a x
y z a k l q z
awk '{
    for (i=1; i<=NF; i++) 
        if ($i == "a" && n < 3) {
            $i = "Z"
}' alphabet

Or, "one-liner"-ed

awk '{for (i=1;i<=NF;i++) if ($i=="a" && n++<3) $i="Z"; print}' alphabet
| improve this answer | |
  • While this may answer the question, it would be a better answer if you could provide some explanation why it does so. – DavidPostill Dec 12 '15 at 0:22
  • 1
    It's not very complicated code. Do you have any particular questions? – glenn jackman Dec 12 '15 at 4:50
  • No, but future readers of the answer might ... – DavidPostill Dec 12 '15 at 8:16
  • 1
    Note that this will find only occurrences of a that are separate words.  While this is true of the example data, it is not specified as being true of the real data.  Also, if this changes a line that contains strings of multiple spaces (e.g., c   a   t        d o g), it will cause those spaces to collapse to single ones (c Z t d o g). – G-Man Says 'Reinstate Monica' Dec 12 '15 at 21:56

Perl to the rescue:

perl -pe '$c++ while $c < 3 && s/a/Z/' alphabet
| improve this answer | |
  • -1 He said using sed and/or awk, and you didn't even mention them – barlop Dec 11 '15 at 22:16
  • Compact and beautiful. You also have Perl in most cases you've got Bash – SΛLVΘ Dec 12 '15 at 5:41

Here, the sed way

sed -E ':a;N;$!ba;s#a#Z#;s#a#Z#;s#a#Z#' alphabet

Since sed normaly works on lines, any command to sed will act only on 1 line at a time. To be able to replace only the first 3 occurances we need to first make the whole file a single selection on which we will do our 3 replacements. Otherwise we will do 3 replacements on each line.

  • :a creates a label
  • N appends next line into patterns space
  • $! skips last newline
  • ba branches to label a

We have now selected the whole file and will be acting on that space instead on one line at a time, do 3 replacements of "a" with "Z".

The above command will only work on GNU sed, more general but a bit uglier version that should work on non-GNU sed:

sed -e ':a' -e 'N' -e '$!ba' -e 's#a#Z#' -e 's#a#Z#' -e 's#a#Z#' alphabet

EDIT: As suggested in the comments, adding version which uses g command to first replace all occurrences of 'a' with 'Z' and then replace all occurrences of 'Z' after 3th with 'a' again, which effectively leads to replacing only the first 3 occurrences of 'a'. This way you can change the last number to reflect the number of substitutions you need.

sed -e ':a;N;$!ba;s#a#Z#g;s#Z#a#g4' alphabet

| improve this answer | |
  • In order to have a more general solution, I'd set number of chars (+1) to be replaced: ':a;N;$!ba;s#\n#<NL>#g;s#a#Z#g;s#Z#a#4g;s#<NL>#\n#g' i.e. replace all occurrences with "Z", then put back "a" in place starting from the 4th occurrence of "Z". – SΛLVΘ Dec 12 '15 at 7:54
  • 1
    Actually I think s#\n#<NL>#g / s#<NL>#\n#g is not needed... – EvilTorbalan Dec 12 '15 at 10:09
  • @SalvoF I have considered this but in my tests it was not working in some versions of GNU sed when using gN > 4, probably a bug not sure... – EvilTorbalan Dec 12 '15 at 10:23
  • Which versions? I tried three different ones, one of which running on Vista (where I had to use double quotes instead of single, and to double newlines' backslash) – SΛLVΘ Dec 12 '15 at 11:12

The awk solution that has been posted assumes that all the occurrences of a are separate words.  While this is true of the example data, it is not specified as being true of the real data.  The following awk solution is more in the spirit of the perl solution that has been posted:

awk '{ while (changes < 3  &&  sub("a", "Z") > 0) changes++; print }' alphabet

This replaces (substitutes) occurrences of a with Z until the changes counter reaches 3.  Of course, to actually change the file, you will need to do something like

awk '{while (c < 3 && sub("a","Z")>0) c++; print}' alphabet > t && cp t alphabet && rm t

where t is a temporary file.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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