1

I need to replace variables with square brackets in a file:

a=0
foo[1]=0
foo[2]=0

I want to replace foo[1]=0 to foo[1]=2.

To change the values a I use:

func_update_value () {
     field=$1
     newvalue=$2
     sed -i "s/^\($field=\).*/\1$newvalue/" file
}

field="a"
value="2"
func_update_value $field $value

but the same command does not work for foo[x]...

func_update_value () {
     field=$1
     newvalue=$2
     sed -i "s/^\($field=\).*/\1$newvalue/" file
}    
n=1
field="foo[$n]"
value="2"
func_update_value $field $value

What is the right syntax with sed?

The change is made by a function, is it possible to use single sed command for both cases?

2
  • 1
    What have you tried? Where did you get stuck?
    – slhck
    Feb 28, 2019 at 11:04
  • Sorry, I modified the question ...
    – Jax2171
    Feb 28, 2019 at 11:23

1 Answer 1

1

[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.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .