Make you last but one .* lazy, make the last capturing group optional, and append the $, end of string anchor:
^\[(.*?)] \[(.*?)] \[(.*?)] (/.*?)(\?.*)?$ ^ ^^
See the regex demo
- The
.*? in the (/.*?) group should be lazy since we need to allow the subsequent group to be filled with as many chars as possible (\?.*)? - must be optional as the text can be absent $ is necessary since the preceding 2 groups are optional, and thus no text at the end of the string might get matched. This way, we require the regex engine to grab the rest of the line.
See a Java demo:
Pattern pattern = Pattern.compile("^\\[(.*?)] \\[(.*?)] \\[(.*?)] (/.*?)(\\?.*)?$"); String[] ss = { "[txt1] [txt2] [txt3] /some/long/path?params=1,2,3", "[txt1] [txt2] [txt3] /path/", "[txt1] [txt2] [txt3] /"}; for (String s: ss) { Matcher matcher = pattern.matcher(s); while (matcher.find()){ System.out.println("Next match for \"" + s + "\"" ); System.out.println(matcher.group(1)); System.out.println(matcher.group(2)); System.out.println(matcher.group(3)); System.out.println(matcher.group(4)); System.out.println(matcher.group(5)); } }
Output:
Next match for "[txt1] [txt2] [txt3] /some/long/path?params=1,2,3" txt1 txt2 txt3 /some/long/path ?params=1,2,3 Next match for "[txt1] [txt2] [txt3] /path/" txt1 txt2 txt3 /path/ null Next match for "[txt1] [txt2] [txt3] /" txt1 txt2 txt3 / null