Bash error: "readonly variable"
Bash refuses to reassign a variable that was marked `readonly` or `declare -r`. Unset is also blocked. Restart the shell or rename the variable.
Error String
bash: VAR: readonly variable
Tldr
A variable was marked `readonly` (often by `set -o`, a sourced library, or a previous run of the same script in an interactive shell). It cannot be reassigned or unset for the lifetime of the shell. Start a fresh shell or use a different variable name.
Cause
Common sources: `declare -r VAR=…`, `readonly VAR=…`, or a sourced library that exports a constant. Re-sourcing the library in an interactive session leaves the constant in place from the previous run.
Repro
readonly NAME=alice
NAME=bob # bash: NAME: readonly variableFix
# In a script, just don't reassign. If you sourced the file
# interactively and need to rerun, start a new shell:
exec bash
# In a script, prefer local variables in functions:
my_fn() {
local NAME=bob
...
}Explanation
Use `local` for variables that should not leak out of a function. Use `readonly` for genuine constants only — and put them in a single initialization block that is safe to source once.
Faq
Q
Can I unset a readonly variable?
A
No. The attribute lasts for the life of the shell; start a new process to reset it.
Q
Why did it work the first time and fail on re-run?
A
In an interactive shell, sourcing the script twice hits the already-locked constant from the first run.
Deep Dive
Heading
Readonly is permanent for the shell session
Body
A variable marked with `readonly` or `declare -r` cannot be reassigned or unset for the life of that shell. There is no way to remove the attribute — the only escape is a new shell process. That makes it easy to break a script by sourcing a library twice: the second `readonly CONFIG=...` fails because the first run already locked it. Guard the definition with `[ -z "${CONFIG:-}" ] && readonly CONFIG=...`, or use an include guard at the top of the library.
Heading
Shell-managed names are readonly too
Body
Bash reserves several names. `UID`, `EUID`, `PPID`, `SHELLOPTS`, `BASHOPTS` and `BASH_VERSINFO` are readonly by the shell, so assigning to them fails regardless of your code. If a loop variable or helper collides with one of these, rename yours — lowercase names for script-local variables avoid every collision with shell and environment conventions, and also make it obvious which values are yours.
Checklist
Check whether a library is being sourced twice.
Add an include guard so constants are defined once.
Rename variables that collide with UID, EUID, PPID or SHELLOPTS.
Use lowercase names for script-local variables.