Replace the first occurence of a pattern in a file that may contain a slash
While:
sed "0,\~$var~s~$var~replacement~"
Can be used to change the regex delimiter, embedding variable expansions inside sed
(or any other interpreter) code is a very unwise thing to do in the general case.
First, here, the delimiter is not the only character that needs to be escaped. All the regular expression operators need to as well.
But more importantly, and especially with GNU sed
, that's a command injection vulnerability. If the content of $var
is not under your control, it's just as bad as passing arbitrary data to eval
.
Try for instance:
$ var='^~s/.*/uname/e;#'
$ echo | sed "0,\~$var~s~$var~replacement~"
Linux
The uname
command was run, thankfully a harmless one... this time.
Non-GNU sed
implementations can't run arbitrary commands, but can overwrite any file (with the w
command), which is virtually as bad.
A more correct way is to escape the problematic characters in $var
first:
NL='
'
case $var in
(*"$NL"*)
echo >&2 "Sorry, can't handle variables with newline characters"
exit 1
esac
escaped_var=$(printf '%s\n' "$var" | sed 's:[][\/.^$*]:\\&:g')
# and then:
sed "0,/$escaped_var/s/$escaped_var/replacement/" < file
Another approach is to use perl
:
var=$var perl -pe 's/\Q$ENV{var}\E/replacement/g && $n++ unless $n' < file
Note that we're not expanding the content of $var
inside the code passed to perl
(which would be another command injection vulnerability), but are letting perl
expand its content as part of its regexp processing (and within \Q...\E
which means regexp operators are not treated specially).
If $var
contains newline characters, that may only match if there's only one at the end. Alternatively, one may pass the -0777
option so the input be processed as a single record instead of line-by-line.