Sed Only The Last Match Pattern

How to replace only last match in a line with sed?

Copy pasting from something I've posted elsewhere:

$ # replacing last occurrence
$ # can also use sed -E 's/:([^:]*)$/-\1/'
$ echo 'foo:123:bar:baz' | sed -E 's/(.*):/\1-/'
foo:123:bar-baz
$ echo '456:foo:123:bar:789:baz' | sed -E 's/(.*):/\1-/'
456:foo:123:bar:789-baz
$ echo 'foo and bar and baz land good' | sed -E 's/(.*)and/\1XYZ/'
foo and bar and baz lXYZ good
$ # use word boundaries as necessary - GNU sed
$ echo 'foo and bar and baz land good' | sed -E 's/(.*)\band\b/\1XYZ/'
foo and bar XYZ baz land good

$ # replacing last but one
$ echo 'foo:123:bar:baz' | sed -E 's/(.*):(.*:)/\1-\2/'
foo:123-bar:baz
$ echo '456:foo:123:bar:789:baz' | sed -E 's/(.*):(.*:)/\1-\2/'
456:foo:123:bar-789:baz

$ # replacing last but two
$ echo '456:foo:123:bar:789:baz' | sed -E 's/(.*):((.*:){2})/\1-\2/'
456:foo:123-bar:789:baz
$ # replacing last but three
$ echo '456:foo:123:bar:789:baz' | sed -E 's/(.*):((.*:){3})/\1-\2/'
456:foo-123:bar:789:baz

Further Reading:

  • Buggy behavior if word boundaries is used inside a group with quanitifiers - for example: echo 'it line with it here sit too' | sed -E 's/with(.*\bit\b){2}/XYZ/' fails
  • Greedy vs. Reluctant vs. Possessive Quantifiers
  • Reference - What does this regex mean?
  • sed manual: Back-references and Subexpressions

sed: print all lines THROUGH the LAST match of pattern A, then print ONLY lines that match pattern B

without tac, a double-pass approach with awk

$ awk 'NR==FNR{if(/^X$/) lx=NR; next} FNR<=lx || /^Y$/' file{,}

a
b
X
d
X
Y
Y

mark the last index of X and print all before that index and other matching pattern.

sed replace last line matching pattern

Not quite sed only:

tac file | sed '/a/ {s//c/; :loop; n; b loop}' | tac

testing

% printf "%s\n" a b a b a b | tac | sed '/a/ {s//c/; :loop; n; b loop}' | tac
a
b
a
b
c
b

Reverse the file, then for the first match, make the substitution and then unconditionally slurp up the rest of the file. Then re-reverse the file.

Note, an empty regex (here as s//c/) means re-use the previous regex (/a/)

I'm not a huge sed fan, beyond very simple programs. I would use awk:

tac file | awk '/a/ && !seen {sub(/a/, "c"); seen=1} 1' | tac

How to select only the last match using sed?

Do you need to use sed?

awk '$1 == "*" && $2 == "x" {v=$3} END {print v}' input

If you must use sed, it's probably easier just to pipe it to a 2nd instance which prints only the last line:

< input sed -n '/^\* *x */s///p' | sed -n '$s/ .*//p'

(Cheating a bit here, making some simplifying assumptions and using the second sed to get only the first column.)

sed regex the last match

Please bear in mind that

  • (?:...) is a non-capturing group and POSIX regex does not support this construct
  • (?!...) is a negative lookahead and POSIX regex does not support this construct either.

To mask all content in between the last pair of double quotation marks, you can use

sed -r 's/(.*)".*"/\1"XXXX"/' file
sed 's/\(.*\)".*"/\1"XXXX"/' file

See the sed demo:

s='"192.xx2" "someting" "another2321SD"'
sed -r 's/(.*)".*"/\1"XXXX"/' <<< "$s"
# => "192.xx2" "someting" "XXXX"

sed: return last occurrence match until end of file

If you have tac available:

tac INPUTFILE | sed '/^Statistics |/q' | tac

Sed seems to replace only the last occurrence in global string substitution

Try the following:

echo "0+223+141+800+450+1*(106+400)+1*(1822+500)+1*(183+400)" |
sed 's/\(\*([^+]*\)+/\1suma/g'

which yields:

0+223+141+800+450+1*(106suma400)+1*(1822suma500)+1*(183suma400)

The trick is to avoid sed's invariably greedy matching, so expression [^+]* is used instead of .*, so as to only match up to the next +.


Note that your attempt didn't only replace the last occurrence of your intended pattern, but - due to greedy matching - found only 1 match spanning multiple intended patterns, which it replaced:

\*\(.*\)+ matched *(106+400)+1*(1822+500)+1*(183+ - everything from the first * literal to the last + literal, and capture group \1 therefore expanded to (106+400)+1*(1822+500)+1*(183

Why is this sed command only working on every other match?

To answer the question as to why it seemed to be skipping every other occurrence
(as fleshed out in the comments of Sundeep's answer. See his answer to work around this)

The apparent skipping was just an illusion. sed is greedy; it found the first occurrence of PATTERN and up to and including the next line starting with a >. It then deletes everything between (as instructed). sed then continues where it left off and as such doesn't "see" that last line as a new occurrence

to be clear:

>PATTERN     <--- sed see's the first occurrence here------------------|
a |(this whole
a |chunk is
a |considered
|by sed)
>PATTERN <--- then matches up to here (the next occurence of ">")--|
b <--- then continues from here "missing" the match of PATTERN above
b
b

>PATTERN
c
c
c



Related Topics



Leave a reply



Submit