1

I am needing to search and replace a string in a file with another string. Normally that can be done using

sed -i 's/old-text/new-text/g' input.txt

however that will not work if the replacement strings are options for commands like

INCPATH = -I. -isystem /usr/include/x86_64-linux-gnu/qt5 -isystem /usr/include/x86_64-linux-gnu/qt5/QtWidgets -isystem /usr/include/x86_64-linux-gnu/qt5/QtGui -isystem /usr/include/x86_64-linux-gnu/qt5/QtCore -I.

I have these strings stored in bash variables

new_string=$(sed -n '/INCPATH       = /,/^$/p' Makefile_qmake)
old_string=$(sed -n '/INCPATH       = /,/^$/p' Makefile_skeleton)

I need to perform the replacement, replacing the old_string with the new_string in the file.

sed -i "s/'${old_string}'/'${new_string}'/" Makefile

The problem is that sed thinks that the replacement strings contain commands for itself. I do not want the commands in the replacement strings to be interpreted as commands. I want it to perform a blind search and replace, without expanding the commands.

Barmar
  • 741,623
  • 53
  • 500
  • 612
Galaxy
  • 2,363
  • 2
  • 25
  • 59
  • `sed` might not be the best tool for this. `perl` has a quoting operator that will automatically escape all the characters that have special meaning in a regexp. – Barmar May 16 '20 at 05:51
  • @Barmar Provide an example? – Galaxy May 16 '20 at 05:54
  • https://perldoc.perl.org/functions/quotemeta.html – Barmar May 16 '20 at 05:55
  • Could you provide an example of `old_string`, `new_string`, and the expected output? – ThatsWhatSheCoded May 16 '20 at 06:58
  • @ThatsWhatSheCoded both `old_string` and `new_string` are parts of Makefiles, either options or rules. – Galaxy May 16 '20 at 07:00
  • `INCPATH = -I. -isystem /usr/include/x86_64-linux-gnu/qt5 -isystem /usr/include/x86_64-linux-gnu/qt5/QtWidgets -isystem /usr/include/x86_64-linux-gnu/qt5/QtGui -isystem /usr/include/x86_64-linux-gnu/qt5/QtCore -I. -I. -I/usr/lib/x86_64-linux-gnu/qt5/mkspecs/linux-g++-64` – Galaxy May 16 '20 at 07:00
  • `ui_mainwindow.h: sources/mainwindow.ui /usr/lib/x86_64-linux-gnu/qt5/bin/uic sources/mainwindow.ui -o ui_mainwindow.h` – Galaxy May 16 '20 at 07:01
  • @ThatsWhatSheCoded They may have `-`, `/`, `\`, newline, or tab – Galaxy May 16 '20 at 07:02
  • I'm not sure I understand you. When the commands in `old_string` and `new_string` are to be considered dumb text, why don't you use `sed -i '/^INCPATH/s/Makefile_qmake/Makefile_skeleton/' Makefile`? – Walter A May 16 '20 at 09:43

2 Answers2

0

I would recommend changing the sed delimiter if your pattern string or replacement string contain the '/' character:

old_string=${old_string//[[:space:]]/ }
new_string=${new_string//[[:space:]]/ }
sed -i "s;'${old_string}';'${new_string}';" Makefile
  • `sed: -e expression #1, char 306: unterminated `s' command` – Galaxy May 16 '20 at 07:10
  • @Galaxy Since you clarified in another comment above that either of the strings may contain tabs and newlines, I have updated my answer to first convert all whitespace to a single space which resolves the `unterminated` error in your above comment. – ThatsWhatSheCoded May 16 '20 at 07:39
0

Use escaping on both old and new strings so as to avoid issues related to special metacharacters:

old_string='your_string_to_find'
new_string='your_string_to_replace_with'

IFS= read -d '' -r < <(sed -e ':a' -e '$!{N;ba' -e '}' -e 's/[&/\]/\\&/g; s/\n/\\&/g' <<< "${new_string}")
replaceEscaped=${REPLY%$'\n'}
searchEscaped=$(sed -e 's/[^^]/[&]/g; s/\^/\\^/g; $!a\'$'\n''\\n' <<<"${old_string}" | tr -d '\n')

sed -i "s/${searchEscaped}/${replaceEscaped}/g" file

Please refer to Is it possible to escape regex metacharacters reliably with sed.

Ryszard Czech
  • 18,032
  • 4
  • 24
  • 37