📦 declare — Variable Attributes Builtin

What is declare?

The declare builtin sets variables and assigns attributes that control their behavior — integer-only, read-only, uppercase, lowercase, indexed array, associative array, and more. It gives bash variables a degree of type safety and structure that plain assignment cannot provide.

declare is also used to inspect variables — listing all defined variables and functions, or displaying a variable's current attributes and value. The synonym typeset is identical and exists for ksh compatibility.

Syntax:
declare [OPTIONS] [NAME[=VALUE] ...]

declare -p NAME   # print variable with attributes
declare -p        # print ALL declared variables
declare -f        # print all function definitions
declare -F        # print function names only

Attribute Flags at a Glance

-i

Integer — arithmetic only, non-numeric assignments become 0

-r

Read-only — cannot be changed or unset after declaration

-x

Export — mark for export to child processes (like export)

-u

Uppercase — value auto-converted to uppercase on assignment

-l

Lowercase — value auto-converted to lowercase on assignment

-a

Indexed array — standard numerically-indexed array

-A

Associative array — key-value map (hash/dictionary)

-n

Nameref — variable is a reference to another variable

-g

Global — declare at global scope even inside a function

-p

Print — display current value and attributes

-f

Function — operate on functions, not variables

+flag

Remove an attribute (e.g., +r removes read-only)

Flag prefix: Use - to set an attribute, + to remove it. For example declare -r VAR makes it read-only; declare +x VAR removes the export attribute. Note: read-only (-r) cannot be removed once set.

Examples

1
Integer Variables with -i

Integer-attributed variables only hold numeric values and support direct arithmetic without $(( )):

# Declare integer variables
declare -i count=0
declare -i total=100
declare -i pages

# Arithmetic works directly
count=count+5
total=total*2
echo "count=$count  total=$total"

# Non-numeric assignment becomes 0
count="hello"
echo "count=$count"    # prints 0

# Practical: loop counter
declare -i errors=0
for logfile in /var/log/*.log; do
    grep -q "ERROR" "$logfile" && errors=errors+1
done
echo "Files with errors: $errors"
Output:
count=5 total=200 count=0 Files with errors: 3
💡 Integer arithmetic: With -i, you can write count=count+1 instead of count=$((count+1)). The variable is always treated as an arithmetic expression on assignment.
2
Read-Only Variables with -r

Constants that cannot be changed or unset — ideal for configuration values:

# Declare constants at top of script
declare -r SCRIPT_VERSION="2.1.4"
declare -r CONFIG_DIR="/etc/myapp"
declare -r LOG_FILE="/var/log/myapp.log"
declare -r MAX_RETRIES=3

# Attempting to change raises an error
SCRIPT_VERSION="3.0"      # bash: SCRIPT_VERSION: readonly variable
unset SCRIPT_VERSION      # bash: unset: SCRIPT_VERSION: cannot unset

# Combine with -i for a read-only integer
declare -ri MAX_CONNECTIONS=100
⚠️ Permanent in session: Once -r is set, it cannot be removed with +r — the variable is read-only for the life of the shell session. Declare constants early and intentionally.
💡 Convention: Using declare -r for constants is more explicit and safer than plain assignment. It documents intent and prevents accidental overwrite later in a long script.
3
Uppercase and Lowercase with -u and -l

Auto-case conversion on every assignment — no need to call tr or parameter expansion:

# Uppercase — any assignment is auto-converted
declare -u HOSTNAME_UPPER
HOSTNAME_UPPER=$(hostname)
echo "$HOSTNAME_UPPER"     # BAMBUSTUDIO

# Lowercase — useful for normalizing user input
declare -l username
read -p "Enter username: " username
# Whatever they type becomes lowercase
echo "Normalized: $username"

# Practical: normalize environment variable
declare -l log_level
log_level="${LOG_LEVEL:-info}"
case "$log_level" in
    debug) echo "Verbose logging on" ;;
    info)  echo "Standard logging" ;;
    warn)  echo "Warnings only" ;;
    *)     echo "Unknown level: $log_level" ;;
esac
Output:
BAMBUSTUDIO Normalized: craig Standard logging
Case conversion applies on every assignment — not just the first. Reassigning the variable later still auto-converts. This makes -u and -l reliable normalizers throughout a script's life.
4
Indexed Arrays with -a

Explicitly declare indexed arrays — good practice even though bash creates them implicitly:

# Explicit array declaration
declare -a servers
declare -a -r ENVIRONMENTS=("dev" "staging" "prod")   # read-only array

# Populate
servers=("web01" "web02" "db01" "db02")
servers+=("cache01")

# Iterate
for srv in "${servers[@]}"; do
    echo "Checking: $srv"
done

# Inspect with declare -p
declare -p servers
declare -p output:
declare -a servers=([0]="web01" [1]="web02" [2]="db01" [3]="db02" [4]="cache01")
💡 declare -p for debugging: declare -p varname shows the exact internal representation of any variable — attributes, indices, and values. Invaluable for debugging arrays that aren't behaving as expected.
5
Associative Arrays with -A

Key-value maps — bash's equivalent of a dictionary or hash. Must be explicitly declared with -A:

# Must declare -A before use — no implicit creation
declare -A server_roles
declare -A port_map
declare -A config

# Assign key-value pairs
server_roles["web01"]="nginx"
server_roles["db01"]="postgresql"
server_roles["cache01"]="redis"

# Or assign all at once
declare -A port_map=(
    [http]=80
    [https]=443
    [ssh]=22
    [mysql]=3306
    [redis]=6379
)

# Lookup
echo "web01 runs: ${server_roles[web01]}"
echo "HTTPS port: ${port_map[https]}"

# Iterate keys and values
for service in "${!port_map[@]}"; do
    printf "%-10s %s\n" "$service" "${port_map[$service]}"
done

# Check if key exists
if [[ -v port_map[redis] ]]; then
    echo "Redis port defined: ${port_map[redis]}"
fi
Output:
web01 runs: nginx HTTPS port: 443 http 80 https 443 ssh 22 mysql 3306 redis 6379 Redis port defined: 6379
⚠️ -A is mandatory: Unlike indexed arrays, associative arrays are never created implicitly. Forgetting declare -A and trying to use string keys will silently fail or produce unexpected results.
6
Export Variables with -x

declare -x is equivalent to export — marks a variable for inheritance by child processes:

# These are equivalent
export PATH="/usr/local/bin:$PATH"
declare -x PATH="/usr/local/bin:$PATH"

# Combine attributes — exported read-only integer
declare -xri MAX_PROCS=8

# Export an array (note: arrays are NOT exported to subshells)
declare -x DB_HOST="db.example.com"
declare -x DB_PORT="5432"
declare -x DB_NAME="production"

# Remove export attribute without unsetting variable
declare +x DB_NAME
# DB_NAME still exists locally but won't be inherited by children

# List all exported variables
declare -x
Arrays and export: Bash cannot export arrays to child processes. If you need to pass array data to a subshell, serialize it: export SERVERS="${servers[*]}" and re-split in the child.
7
Nameref Variables with -n

Namerefs create a reference to another variable by name — useful for passing variable names into functions:

# Basic nameref
declare -n ref=myvar
myvar="hello"
echo "$ref"        # prints: hello
ref="world"
echo "$myvar"      # prints: world  (same variable)

# Practical — function that modifies a caller's variable by name
fill_array() {
    declare -n _arr=$1       # _arr is a nameref to whatever name was passed
    _arr=("alpha" "beta" "gamma")
}

declare -a my_list
fill_array my_list
echo "${my_list[@]}"   # alpha beta gamma

# Another pattern — function returns result into named variable
get_hostname() {
    declare -n _result=$1
    _result=$(hostname -f)
}

get_hostname FQDN
echo "This host: $FQDN"
💡 Avoid collision: When using namerefs in functions, prefix the local nameref name with an underscore (e.g., _arr) to avoid colliding with the variable name passed in. If the caller passes arr and your nameref is also named arr, bash will complain about a circular reference.
8
Inspecting Variables and Functions with -p, -f, -F

declare is also an inspection tool — see what is defined and how:

# Inspect a single variable — shows attributes and value
declare -p PATH
declare -p BASH_VERSION
declare -p myarray

# Inspect all currently declared variables
declare -p

# List all defined functions (names only)
declare -F

# Show full function definitions
declare -f

# Show definition of one specific function
declare -f cleanup

# Practical — capture current environment to restore later
declare -p > /tmp/env_snapshot.sh
# Later, in another script:
source /tmp/env_snapshot.sh
declare -p examples:
declare -rx SCRIPT_VERSION="2.1.4" declare -a servers=([0]="web01" [1]="web02" [2]="db01") declare -A port_map=([http]="80" [https]="443" [ssh]="22")
💡 Reading declare -p output: The flags shown tell you everything — -rx means read-only and exported, -a means indexed array, -A means associative array. This output is also valid bash — you can source it to recreate the variable exactly.

declare Inside Functions — Scope Rules

Variables declared with declare inside a function are local to that function by default — same as using local:

#!/bin/bash

my_function() {
    declare local_var="I am local"      # local to function
    declare -g global_var="I am global" # -g forces global scope
    declare -i local_count=0            # local integer
    echo "Inside: $local_var"
}

my_function
echo "Outside local_var: '${local_var}'"   # empty — was local
echo "Outside global_var: '$global_var'"   # visible — was -g
Output:
Inside: I am local Outside local_var: '' Outside global_var: 'I am global'
⚠️ declare vs local: Inside functions, declare and local both create local variables. The difference is that declare can set attributes (-i, -r, etc.) while local cannot. Use declare -g when you need a global variable with attributes set from inside a function.

Quick Reference

CommandWhat it does
declare -i VAR=0Integer variable — arithmetic on assignment
declare -r VAR="x"Read-only constant — cannot be changed
declare -x VAR="x"Export to child processes
declare -u VARAuto-uppercase on assignment
declare -l VARAuto-lowercase on assignment
declare -a ARRIndexed array declaration
declare -A MAPAssociative array (required — no implicit creation)
declare -n REF=OTHERNameref — alias for another variable
declare -g VARGlobal scope even inside a function
declare -ri VAR=NCombine flags — read-only integer
declare -p VARPrint variable with all attributes
declare -pPrint ALL declared variables
declare -fPrint all function definitions
declare -FPrint function names only
declare +x VARRemove export attribute

declare vs Related Builtins

BuiltinRelationship to declare
typesetIdentical to declare — ksh compatibility synonym
localCreates function-local variables but cannot set attributes
exportEquivalent to declare -x — marks variable for export
readonlyEquivalent to declare -r — sets read-only attribute
unsetRemoves a variable or function (blocked by -r)