This is an enhancement
of Hashbrown’s answer
(and wef’s answer
to a very similar question).
We can remove the issue of the special meaning of various special characters
and strings (
\1, …, whatever, and the
by removing the special characters.
Specifically, we can convert everything to hex;
then we have only
f to deal with.
This example demonstrates the principle:
$ echo -n '3.14' | xxd
0000000: 332e 3134 3.14
$ echo -n 'pi' | xxd
0000000: 7069 pi
$ echo '3.14 is a transcendental number. 3614 is an integer.' | xxd
0000000: 332e 3134 2069 7320 6120 7472 616e 7363 3.14 is a transc
0000010: 656e 6465 6e74 616c 206e 756d 6265 722e endental number.
0000020: 2020 3336 3134 2069 7320 616e 2069 6e74 3614 is an int
0000030: 6567 6572 2e0a eger..
$ echo "3.14 is a transcendental number. 3614 is an integer." | xxd -p \
| sed 's/332e3134/7069/g' | xxd -p -r
pi is a transcendental number. 3614 is an integer.
whereas, of course,
sed 's/3.14/pi/g' would also change
The above is a slight oversimplification; it doesn’t account for boundaries.
Consider this (somewhat contrived) example:
$ echo -n 'E' | xxd
0000000: 45 E
$ echo -n 'g' | xxd
0000000: 67 g
$ echo '$Q Eak!' | xxd
0000000: 2451 2045 616b 210a $Q Eak!.
$ echo '$Q Eak!' | xxd -p | sed 's/45/67/g' | xxd -p -r
combine to form
s/45/67/g command rips it apart from the inside.
2671, which is
We can prevent that by separating the bytes of data in the search text,
the replacement text and the file with spaces.
Here’s a stylized solution:
xxd -p -- "$@" | sed 's/../& /g' | tr -d '\n'
xxd -p -r -- "$@"
left=$( printf '%s' "$search" | encode)
right=$(printf '%s' "$replacement" | encode)
encode path/to/the/file | sed "s/$left/$right/g" | decode
I defined an
encode function because I used that functionality three times,
and then I defined
decode for symmetry.
If you don’t want to define a
decode function, just change the last line to
encode path/to/the/file | sed "s/$left/$right/g" | xxd -p –r
Note that the
encode function triples the size of the data (text)
in the file, and then sends it through
sed as a single line
— without even having a newline at the end.
GNU sed seems to be able to handle this;
other versions might not be able to.
Also, this does not modify the file in place;
you’ll need to write the output to a temporary file
and then copy it over the original file
(or one of the other tricks for doing that).
As an added bonus, this solution handles multi-line search and replace
(in other words, search and replacement strings that contain newline(s)).