Use of read-only variables in shell scripts
It sounds like you might think that readonly
does more than it really does. For one thing, readonly status is not exported into the environment or inherited by child processes:
$ declare -rx LOGS=hello
$ LOGS=goodbye
bash: LOGS: readonly variable
$ bash -c 'echo "$LOGS"'
hello
$ bash -c 'LOGS=goodbye; echo "$LOGS"'
goodbye
$
shell script set -e with readonly variable
This is because the line readonly V="$(cat non-existant-file)"
is not a simple assignment: it is the composition of an assignment that fails, followed by the instruction readonly V
, which succeeds.
That explains the behavior you observed, and this Bash pitfall is mentioned for a similar construct (local
) in the documentation BashFAQ/105 indicated by @codeforester.
So, if you try instead the following code, you should observe the behavior you expect:
#!/bin/bash
set -ex
V=$(cat non-existant-file)
readonly V
echo "var V: $V"
Minor remarks:
I corrected the shebang that should be
#!/usr/bin/env bash
or#!/bin/bash
, not!#/bin/bash
I replaced
V="$(cat non-existant-file)"
withV=$(cat non-existant-file)
because the quotes are unnecessary here.
How can I make a read-only variable?
You can make use of readonly
:
$ var="hello"
$ readonly var
$ echo $var
hello
$ var="bye"
sh: var: readonly variable
readonly' exit status in bash
You can do it in two steps without needing a temporary variable:
foo="$(false)"
echo $?
readonly foo
Alternatively you can do
readonly foo="$(false)" status="$?"
to capture both the output and the exit status simultaneously.
How can I limit readonly variable scope to a function?
Replace:
readonly local foo="bar"
with:
local -r foo="bar"
The issue is that readonly local foo="bar"
defines two readonly variables: one named local
and one namedfoo
. It does not create any local variables.
By contrast, local -r foo="bar"
creates a variable named foo
which is both local and readonly.
As David C Rankin points out, once you have created a global read-only variable, you cannot unset it. You need to close your existing shell and start a new one.
Unset readonly variable in bash
Actually, you can unset a readonly variable. but I must warn that this is a hacky method. Adding this answer, only as information, not as a recommendation. Use it at your own risk. Tested on ubuntu 13.04, bash 4.2.45.
This method involves knowing a bit of bash source code & it's inherited from this answer.
$ readonly PI=3.14
$ unset PI
-bash: unset: PI: cannot unset: readonly variable
$ cat << EOF| sudo gdb
attach $$
call unbind_variable("PI")
detach
EOF
$ echo $PI
$
A oneliner answer is to use the batch mode and other commandline flags, as provided in F. Hauri's answer:
$ sudo gdb -ex 'call unbind_variable("PI")' --pid=$$ --batch
sudo
may or may not be needed based on your kernel's ptrace_scope settings. Check the comments on vip9937's answer for more details.
Bash local AND readonly variable
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
Related Topics
Handling Input Confirmations in Linux Shell Scripting
How to Install Apxs Module on Apache 2.4.6
How to Properly Set Java_Home in /Etc/Environment
Manually Merge Two Files Using Diff
How to Capture Still Image from Webcam on Linux
Ld_Library_Path Doesn't Seem to Work
Maximum Number of Files/Directories on Linux
Difference Between Virtual Page and Page Frame
Does The Linux Scheduler Needs to Be Context Switched
Why How to Cast Sockaddr to Sockaddr_In
Searching for a String in Multiple Files on Linux
Wget-Like Bittorrent Client or Library
What Is The Significance of This_Module in Linux Kernel Module Drivers
Difference Between Tcp_Max_Syn_Backlog and Somaxconn
What Are The Advantages Napi Before The Irq Coalesce