[1]
is not (i.e. not necessarily) literal in the shell. Then it's not (i.e. certainly not) literal as a part of a regex pattern in sed
.
I've checked your previous questions and I guess you're using Bash. The first part of my answer applies to many common shells, including Bash (zsh is the notable exception).
Possible problem in the shell
At first you assign values to variables:
n=1
field="foo[$n]"
value="2"
All this works as you would expect. The unquoted 1
is not special, it doesn't need to be quoted. The quoted 2
might or might not be quoted and it would make no difference. Double-quotes around foo[$n]
make a difference; it's good they are there, this way [
and ]
are nothing special and the content of the field
variable is literally foo[1]
for sure.
But then you have
func_update_value $field $value
where $field
is not quoted. Just after parameter expansion the line is
func_update_value foo[1] 2
and then pattern matching against objects in your current directory (globbing) kicks in, especially:
[…]
Matches any one of the enclosed characters.
This means if you had a file or a directory named foo1
, the line would evaluate to
func_update_value foo1 2
It would be even worse if you set n=125
and you had foo1
and foo5
(and/or foo2
) in the directory. More than one file would match the foo[125]
pattern and the final form of the line would be like
func_update_value foo1 foo5 2
which is definitely not what you want. You probably don't have anything named foo1
in the current directory, so the pattern stays literal at this point and the line
func_update_value foo[1] 2
runs the func_update_value
function with literal arguments foo[1]
and 2
.
If your code doesn't fail at this point, it's only by chance of not having foo1
in the directory, not because of proper coding. Proper coding includes double-quoting all variables. There are few rare scenarios where you'd want not to quote, if you know what you're doing; this is not one of them.
Also note in the function itself there is field=$1
where $1
is not quoted. The problem with unquoted $1
is the same as with unquoted $field
earlier.
Problem in sed
Let's suppose your function properly got the literal foo[1]
argument and its local field
variable now contains the literal string foo[1]
as it should.
The problem is [1]
as a part of a regex pattern in sed
is not literal. Like in the shell, it matches any one of the enclosed characters (except matching is against text, not filenames). foo[125]
would match foo1
or foo2
or foo5
. foo[1]
matches foo1
only.
To make sed
match [
and ]
literally, you need to escape them in the pattern. Instead of field="foo[$n]"
you need
field="foo\[$n\]"
is it possible to use single sed
command for both cases?
Yes. Your current command is like sed "s/^\($field=\).*/\1$newvalue/"
and it will work as long as you keep in mind the value of $field
will be interpreted as a part of the pattern (so [
, .
, \(
, $
etc. are special) and the value of $newvalue
will be interpreted as a part of the replacement (so e.g. \1
is special). And there is the separator you chose (/
) which mustn't occur in any of the two variables, or else it will break or change the syntax.
Final note
The second fix alone (adding backslashes before [
and ]
) would even prevent the shell from expanding […]
, so it would accidentally "fix" the quoting issue in this particular case, for this particular value of the variable.
In general though you should apply both fixes. And you certainly should get used to quoting variables by default, this will save you time and frustration while debugging your future shell scripts. That's why my answer doesn't limit itself to the second fix.