Bash local AND readonly variable

The bash man page summarizes things thusly for the declare command:

    -r   Make names readonly.  These names cannot then be assigned values
         by subsequent assignment statements or unset.

and

   When used in a function, declare and typeset make each name local, as with
   the local command, unless the -g option is supplied.

So if you declare within a function, the variable you declare will be local by default. And if you use the -r option, it will be read-only.

$ cat testv
#!/usr/bin/env bash

test1() {
  declare -r var="$1"
  var="bacon"
}

s=foo
test1 bar
echo "$s - $var"

$ bash testv
testv: line 5: var: readonly variable
foo -

First attempt: local readonly var1

That is the way I used to define it. It is wrong. I will define my variable var1 as local, but it will not be readonly, as you can see on example below, I can change the value of var1, and I don't want that!

:~$ (
>     myfunction()
>     {
>         # Define variable
>         local readonly var1="val1"
>         
>         echo "Readonly output:"
>         readonly | grep -E 'readonly|local|var1'
>         echo ""
> 
>         echo "Local output:"
>         local | grep -E 'readonly|local|var1'
>         echo ""
> 
>         var1="val2"
>         echo "VAR1 INSIDE: ${var1}"
>     }
>     myfunction
>     echo "VAR1 OUTSIDE: ${var1}"
> )
Readonly output:

Local output:
var1=val1

VAR1 INSIDE: val2
VAR1 OUTSIDE:

Second attempt: readonly local var1

This time it will define var1 as readonly, but it will also define a variable called local, so using this way it will not handle local as keyword, it will be a variable name.

Check also that the scope of var1 is not local, in fact it is global, we can see the value of var1 outside the function.

:~$ (
>     myfunction()
>     {
>         # Define variable
>         readonly local var1="val1"
>         
>         echo "Readonly output:"
>         readonly | grep -E 'readonly|local|var1'
>         echo ""
> 
>         echo "Local output:"
>         local | grep -E 'readonly|local|var1'
>         echo ""
> 
>         echo "VAR1 INSIDE: ${var1}"
>     }
>     myfunction
>     echo "VAR1 OUTSIDE: ${var1}"
> )
Readonly output:
declare -r local
declare -r var1="val1"

Local output:

VAR1 INSIDE: val1
VAR1 OUTSIDE: val1

As it should be: local -r var1

This way it will do exactly what I want, it will define var1 as scope local AND readonly.

:~$ (
>     myfunction()
>     {
>         # Define variable
>         local -r var1="val1"
>         
>         echo "Readonly output:"
>         readonly | grep -E 'readonly|local|var1'
>         echo ""
> 
>         echo "Local output:"
>         local | grep -E 'readonly|local|var1'
>         echo ""
> 
>         #var1="val2"
>         echo "VAR1 INSIDE: ${var1}"
>     }
>     myfunction
>     echo "VAR1 OUTSIDE: ${var1}"
> )
Readonly output:
declare -r var1="val1"

Local output:
var1=val1

VAR1 INSIDE: val1
VAR1 OUTSIDE: 

We can define it as below also, but one line is better than two!

local var1="val1"
readonly var1

Tags:

Bash