0

I have a text file and I'm trying to find all instances of Word1 Word2 Word3 in a text file and replace it with Word4 Word2 Word5. Word2 is unknown string but the rest of the words are known.

Here is what I have tried so far

I have a string (...) foobarfoo (...) and I want to replace it with (...) hatbarcar (...)

sed -i 's/foo.*foo/hat.*car/g' data.txt

but the result I get is

(...) hat.*car (...)

So the wildcard is finding the word string I want but I then want to use this same wildcard to write the string which replaces the old one.

Is this possible/anyone have any suggestions?

2

The problem with a

sed -i 's/foo\(.*\)foo/hat\1car/g'

approach is that it would change fooxfoo fooyfoo to hatxfoo fooycar as that .* is greedy.

You can use perl instead with its non-greedy .*? operator.

perl -i -pe 's/foo(.*?)foo/hat$1car/g'

(which also has the advantage of being more portable. That -i comes from perl and is not available in many sed implementations (and when it is, is not interpreted the same by all)).

With GNU sed, and provided $POSIXLY_CORRECT is not in the environment, you could do:

sed -i 's/foo/\n/g;s/\n\([^\n]*\)\n/hat\1car/g;s/\n/foo/g'

That is, replace foo with a character (\n, the line delimiter) that cannot occur in the line so we can use [^\n]* to achieve the non-greedy equivalent.

If POSIXLY_CORRECT is in the environment, then [^\n] would match any character but \ and n as POSIX requires instead of any character but newline. You can always do:

(unset -v POSIXLY_CORRECT; exec sed...)

if you want your script to still work in environments where POSIXLY_CORRECT is set.

1

The replacement string in s/PATTERN/REPLACEMENT/ is not a regular expression.

You will be able to capture what is matched by a bit of the pattern and use it in the replacement if you wish:

sed -r 's/foo(.*)foo/hat\1car/g' file

This will capture anything between two occurrences of foo on the same line, and insert that bit between hat and car. The \1 says "insert anything that was captured by the first parentheses".

Note that .* is "greedy" so if you have foobarfoofoobarfoo, \1 will be barfoofoobar, not bar.

-1

With sed, you can use \( and \) to make a capture group which can be referenced as \1 in the replacement part of the substitution:

 sed 's/foo\(.*\)foo/hat\1car/g'

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.