2

I am trying to add text to the end of a line the first x times it occurs. I know how to do it globally and for the n occurrence. I can't figure out how to do it for the first nth occurrences. An example would be a text.txt file that contains:

This is a test
junk
This is a test
More junk
This is a test
This is a test
This is a test

And, I want to add a '.' at the end of the first three times that "This is a test" occurs. The output I am trying to get is:

This is a test.
junk
This is a test.
More junk
This is a test.
This is a test
This is a test
1
  • @Quasimodo I modified the question to further clarify what I am trying to do. And, you are correct. My work around doesn't do what I thought it did.
    – Garnet
    Oct 22, 2021 at 22:18

3 Answers 3

4

This.*test is the correct regex. The asterisk means "0 or more times the previous character" so This*test wouldn't match any of your lines.

Now, Sed is bad at Arithmetics. For something elegant I suggest Awk:

awk '/This.*test/{c++};{print $0 (c<4 ? "." : "")}' file

I think it suffices to say that c, as any unset variable in Awk, is treated as zero, but let me know if you need further clarification.

2
  • Note that your regular expression would also match Don't dot this: This is not a test.
    – Kusalananda
    Oct 23, 2021 at 7:02
  • @they That is a feature, not a bug. The actual file I am trying to do this on has a fixed beginning and end; but, the stuff in the middle will change.
    – Garnet
    Oct 23, 2021 at 10:56
4

Another variant that avoids doing the regexp matching after all 3 occurrences have already been found:

awk -v n=3 'n && /This is a test/ {n--; $0 = $0 "."}; {print}'

With sed specifically, you could do something like:

sed '
  1 {
    x
    s/^/.../
    x
  }
  /This is a test/ {
    s/$/./
    x
    s/.//
    /./ {
      x
      b
    }
    g
    :1
    $! {
      n
      b 1
    }
  }'

Where we track the number of .s to append as corresponding number of .s in the hold space.

It goes without saying that sed is a lot less appropriate for this kind of task. If the reason for wanting sed is for the -i extension for in-place editing found on a few implementations (borrowed from perl), note that the GNU implementation of awk can also do it with -i inplace, or you could use the real thing:

perl -lpi -e '
  if ($n < 3 && /This is a test/) {
    $n++;
    $_ .= ".";
  }' your-file

If you wanted to add a . after each occurrence of This is a test as opposed to all lines that contain at least one occurrence of This is a test, perl would also be the best choice:

perl -pi -e 's{This is a test\K}{$n++ < 3 ? "." : ""}ge' your-file
0

With perl we could do as shown

perl -lpe '
  $_ = $k == 3 ? next : s/This is a test(?{$k++}).*\K/./r;
' file

Elephants can dance too, albeit the simple steps. Using GNU sed writing in its extended regex mode -E we can store the count as number of newlines in the hold.

K=3
sed -Ee '
  /This is a test/!b
  G
  /(.*\n){'"$K"'}.*\n/!{
    s/\n+/./p;z;H;d
  }
  s/\n+//
  :a;n;ba
' file

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.