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 variable

Fix

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