Regex remove last newline

Viewed 122

Given the following ; delimited string


a;; z
toy;d;hh 
toy
;b;;jj
z;
d;23
d;23td
;;io;
b y;b;12
z
a;b;bb;;;34
z

and this regex

^(?!(?:(a|d))(?:;|$)).*(\s*\z|$)\R*

I am looking to get the full lines whose 1st. column is not a or d, and have the matching lines removed, to get this , after substituting with empty

a;; z
d;23
d;23td
a;b;bb;;;34

Please see the demo

In the Substitution panel, there is a 5th empty line, which needs to be removed.

I have used this \s*\z in this past for this purpose. As implemented here, it does not seem to work.

Any help is appreciated

2 Answers

I think the reason your regex won't remove the last newline, is that it is part of the end of the last part that you want to keep, so without matching it you can't remove it.

So I rewrote the regex to match the line you want to keep, but also to include everything above and below the match that is not another match.

The key difference is using a conditional to only match the newline of the group you want to keep if it is followed by another match.

regex (linebreaks for readability):

((?!(a|d)).*(\s*\z|$)\R*)*
(^(a|d).*(?(?=\R*(.*\s*\R+)*(a|b))\R))
((?!(a|d)).*(\s*\z|$)\R*)*

replace with $4 -->

a;; z
d;23
d;23td
a;b;bb;;;34

For readability I removed some of the non-capturing and string separator logic you had, if they are necessary you can add them back in.

Logic breakdown of the parts:

(?(?=\R*(.*\s*\R+)*(a|b))\R) is the conditional, it only matches the newline \R if (?) it is followed by (?=) any non-matching lines (.*\s*\R+)* that end in a newline followed by (a|b).

The middle part (^(a|d).*(?(?=\R*(.*\s*\R+)*(a|b))\R)) containing this ends up as the replacing group $4. It thus matches lines starting with (a|d), and all but the last match also match the newline at the end of their line.

The beginning and end of the regex ((?!(a|d)).*(\s*\z|$)\R*)* is exactly the same, and matches of all the unneeded stuff so that it gets removed.

You could match what you want to remove, and capture in a group what you want to keep.

To prevent removing the newline sequences between capturing groups, you could use an if clause (? to only match 0+ unicode newline sequences when there is no more line following that starts with [ad];

In the replacement use group 1 $1

^(?:(?![ad];).*\R*)*|^([ad];.*(?:\R[ad];.*)*)(?(?![\s\S]*\R[ad];)\R*)

Explanation

  • ^ Start of line
  • (?: Non capture group
    • (?![ad];) If the line does not start with a or d followed by ;
    • .*\R* Match the whole line and 0+ times a unicode newline sequence
  • )* Close group and repeat 0+ times to match all consecutive lines
  • | Or
  • ^ Start of line
  • ( Capture group 1
    • [ad];.* Match a or d followed by ; and the rest of the line
    • (?: Non capture group
      • \R[ad];.* Match newline, a or d followed by ; and the rest of the line
    • )* Close group and repeat 0+ times to match all consecutive lines
  • ) Close group 1
  • (? If clause, only match a unicode newline sequence if the [ad]; pattern does not occur anymore
    • (?! Negative lookahead, assert what follows is not
      • [\s\S]*\R[ad]; Match the [ad]; pattern
    • ) Close lookahead.
    • \R* If the assertion is true, Match 0+ unicode newline sequences
  • ) Close if clause

    See a Regex demo

Related