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
Read Lines Between Two Keywords
Bridge Serial-Ports Over Network
How to Use Linux Hugetlbfs for Shared Memory Maps of Files
Set Environment Variable in Gdb from Output of Command
Syntax Error Near Unexpected Token 'Do' When Run with Sudo
How to Set a Color Profile with Exiftool
Can't Add File to Git Repository But Can Change/Commit
How to Deploy Files for a Remote Debug Launch in Eclipse Cdt
Using Linux Virtual Mouse Driver
Sharing Stdout Among Multiple Threads/Processes
Docker - Is It Safe to Switch to Non-Root User in Entrypoint
How to Determine The Files Corresponding to a UInput Device
Linux: Move 1 Million Files into Prefix-Based Created Folders
Using Multiple Layers of Quotes in Bash