Bash Scripting 02 ๐ง Variables & Types โ Declaring strings, integers, and read-only values
The previous chapter covered the script execution mechanics. This chapter covers the storage of the data: the variables. In Bash, the variables are untyped by default. A variable can hold the string, the number, or the array, and the shell infers the type from the context. The declare builtin (and its synonyms, typeset and local) is the mechanism for enforcing the type. The critical distinction is between the untyped assignment (VAR=value), which is always the string, and the typed declaration (declare -i, declare -a, declare -r), which adds the semantics. The read-only variable (declare -r) is the constant, and the assignment after the declaration fails. The integer variable (declare -i) performs the arithmetic evaluation on the assignment, and the string value that is not numeric becomes the 0.
Key point: The declare -i treats the variable as the integer and evaluates the arithmetic expressions on the assignment. The declare -r makes the variable read-only and prevents the reassignment. The declare -a declares the indexed array. The declare -A declares the associative array. The declare -x exports the variable to the child processes. The local inside the function limits the scope to the function. The readonly command is the synonym for the declare -r. The unset removes the variable (except the read-only).
Why variable types require a specific mental model
The untyped problem. The VAR=5 assigns the string "5", not the number 5. The VAR+1 is the string concatenation "51", not the arithmetic. The $((VAR + 1)) is the arithmetic because the $(( )) parses the value. The declare -i VAR=5 followed by the VAR+=1 produces 6 because the integer attribute makes the += the arithmetic addition .
The read-only problem. The readonly VAR=value prevents the reassignment. The second assignment fails with the error: “readonly variable.” The unset of the read-only variable fails. The read-only attribute is the permanent for the shell session. The declare -r is the same. The read-only variable is useful for the configuration constants and the paths that must not be overwritten .
The scope problem. The variable is the global by default. The function that assigns the variable without the local overwrites the caller’s value. The local VAR=value inside the function limits the scope to the function. The local is only valid inside the function. The declare inside the function without the local creates the global. The local -r combines the scope and the read-only .
The type attribute problem. The declare attributes are the sticky. The declare -i VAR sets the integer attribute. The VAR=abc becomes the 0 (the invalid integer). The declare -l VAR lowercases the value on the assignment. The declare -u VAR uppercases. The declare -a creates the array. The declare -A creates the associative array. The attributes are the added to the variable, and the declare +i removes the integer attribute .
The export problem. The export VAR makes the variable visible to the child processes. The child process cannot modify the parent’s variable. The declare -x combines the declaration and the export. The export -n removes the export. The environment variable is the exported variable .
The trade-off. The untyped variable is the flexible and the simple. The typed variable is the safer and the self-documenting. The declare -i prevents the string concatenation bugs. The declare -r prevents the accidental overwrites. The practice is the explicit type when the semantics matter.
a. The untyped variables and the declare
The untyped variable is the string.
#!/bin/bash
VAR=5
VAR+=1
echo "$VAR"
# 51 (the string concatenation, not the arithmetic)
VAR2=$((5 + 1))
echo "$VAR2"
# 6 (the arithmetic via the $(( )))
The VAR+=1 is the string append. The $((5 + 1)) is the arithmetic. The untyped variable requires the explicit arithmetic context.
The declare -i makes the variable the integer.
#!/bin/bash
declare -i COUNT=5
COUNT+=1
echo "$COUNT"
# 6 (the arithmetic addition, not the append)
declare -i BAD="abc"
echo "$BAD"
# 0 (the invalid integer becomes the 0)
The declare -i evaluates the arithmetic on the assignment. The COUNT+=1 is the 6. The non-numeric value becomes the 0.
b. The read-only and the exported variables
The readonly prevents the reassignment.
#!/bin/bash
readonly CONFIG_PATH="/etc/myapp/config.conf"
echo "$CONFIG_PATH"
# /etc/myapp/config.conf
CONFIG_PATH="/tmp/override"
# bash: CONFIG_PATH: readonly variable
unset CONFIG_PATH
# bash: unset: CONFIG_PATH: cannot unset: readonly variable
The readonly is the same as the declare -r. The assignment fails. The unset fails. The variable is the permanent for the session.
The export makes the variable visible to the child processes.
#!/bin/bash
export APP_ENV="production"
bash -c 'echo "$APP_ENV"'
# production
LOCAL_VAR="local"
bash -c 'echo "$LOCAL_VAR"'
# (empty, not exported)
The export passes the variable to the child. The unexported variable is the invisible to the child. The export -n removes the export.
c. The arrays and the associative arrays
The indexed array.
#!/bin/bash
declare -a SERVERS=("web01" "web02" "db01")
echo "${SERVERS[0]}"
# web01
echo "${SERVERS[@]}"
# web01 web02 db01
echo "${#SERVERS[@]}"
# 3
SERVERS+=("cache01")
echo "${SERVERS[3]}"
# cache01
The declare -a creates the indexed array. The SERVERS[0] is the first element. The ${SERVERS[@]} is all the elements. The ${#SERVERS[@]} is the count. The += appends.
The associative array.
#!/bin/bash
declare -A PORTS
PORTS[http]=80
PORTS[https]=443
PORTS[ssh]=22
echo "${PORTS[https]}"
# 443
for key in "${!PORTS[@]}"; do
echo "$key โ ${PORTS[$key]}"
done
# http โ 80
# https โ 443
# ssh โ 22
The declare -A creates the associative array. The PORTS[https]=443 is the key-value. The ${!PORTS[@]} is the keys. The ${PORTS[$key]} is the value.
Complete Example Session
# ============================================
# PART 1: THE UNTYPED VARIABLE
# ============================================
VAR=5
VAR+=1
echo "$VAR"
# 51
# ============================================
# PART 2: THE INTEGER VARIABLE
# ============================================
declare -i COUNT=5
COUNT+=1
echo "$COUNT"
# 6
# ============================================
# PART 3: THE INVALID INTEGER
# ============================================
declare -i BAD="abc"
echo "$BAD"
# 0
# ============================================
# PART 4: THE READ-ONLY
# ============================================
readonly CONFIG="/etc/app.conf"
echo "$CONFIG"
# /etc/app.conf
CONFIG="/tmp/x"
# bash: CONFIG: readonly variable
# ============================================
# PART 5: THE EXPORT
# ============================================
export APP_ENV="prod"
bash -c 'echo $APP_ENV'
# prod
# ============================================
# PART 6: THE UNEXPORTED
# ============================================
LOCAL_VAR="local"
bash -c 'echo $LOCAL_VAR'
# (empty)
# ============================================
# PART 7: THE INDEXED ARRAY
# ============================================
declare -a SERVERS=("web01" "web02")
SERVERS+=("db01")
echo "${SERVERS[@]}"
# web01 web02 db01
# ============================================
# PART 8: THE ASSOCIATIVE ARRAY
# ============================================
declare -A PORTS
PORTS[http]=80
PORTS[https]=443
echo "${PORTS[https]}"
# 443
# ============================================
# PART 9: THE LOCAL
# ============================================
my_func() {
local VAR="local"
echo "$VAR"
}
my_func
# local
echo "$VAR"
# (empty, the global is not set)
The nine parts trace the variables: the untyped, the integer, the invalid integer, the read-only, the export, the unexported, the indexed array, the associative array, and the local.
Quick Reference
The declare Options
| Option | Purpose |
|---|---|
-i | The integer |
-r | The read-only |
-a | The indexed array |
-A | The associative array |
-x | The export |
-l | The lowercase |
-u | The uppercase |
-p | The print the declaration |
The Read-Only
| Command | Effect |
|---|---|
readonly VAR=value | The read-only |
declare -r VAR=value | The same |
unset VAR | Fails if the read-only |
The Array Syntax
| Syntax | Purpose |
|---|---|
${arr[0]} | The first element |
${arr[@]} | All the elements |
${#arr[@]} | The count |
${!arr[@]} | The keys (associative) |
arr+=(item) | The append |
The Scope
| Command | Scope |
|---|---|
VAR=value | The global |
local VAR=value | The function |
declare -x VAR | The exported |
Best Practices
โ Do This:
declare -i COUNT=0
<!-- Use the integer for the counters --> # โ
readonly CONFIG="/etc/app.conf"
<!-- Use the read-only for the constants --> # โ
local TEMP="$1"
<!-- Use the local inside the functions --> # โ
โ Don’t Do This:
COUNT=0
COUNT+=1
<!-- The string concatenation, not the arithmetic --> # โ
# Reuse the read-only variable
<!-- The assignment fails --> # โ
# Assign the global inside the function
<!-- Overwrites the caller's value --> # โ
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
The += appends instead of adds | The untyped variable | declare -i |
The invalid integer becomes 0 | The declare -i coercion | Validate before the assignment |
| The read-only unset fails | The read-only attribute | The unset is not allowed |
| The child does not see the variable | Not exported | export VAR |
| The function overwrites the global | No local | local VAR |
Real-World Examples
1. The Integer Counter
declare -i COUNT=0
COUNT+=1
2. The Read-Only Path
readonly LOG_DIR="/var/log/app"
3. The Exported Environment
export APP_ENV="prod"
4. The Indexed Array
declare -a SERVERS=("web01" "web02")
5. The Associative Array
declare -A PORTS=([http]=80 [https]=443)
6. The Array Iteration
for s in "${SERVERS[@]}"; do echo "$s"; done
7. The Local Scope
my_func() { local VAR="local"; }
8. The Unset
unset TEMP
9. The Print the Declaration
declare -p COUNT
10. The Remove the Attribute
declare +i COUNT
Visual
The Type Attributes
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ VAR="5" โ
โ โ The string "5" โ
โ โ VAR+=1 โ "51" โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ declare -i VAR=5 โ
โ โ The integer 5 โ
โ โ VAR+=1 โ 6 โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
The Variable Scope
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Global shell โ
โ โโโ GLOBAL="g" (visible) โ
โ โโโ my_func() โ
โ โโโ local LOCAL="l" โ
โ โโโ GLOBAL="modified" (overwrites) โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ The local is discarded on the return โ
โ The global is modified if no local โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Summary
| Item | Value |
|---|---|
| The untyped | VAR=value (the string) |
| The integer | declare -i VAR |
| The read-only | declare -r or readonly |
| The indexed array | declare -a |
| The associative array | declare -A |
| The export | declare -x or export |
| The local | local VAR |
| The list the declarations | declare -p |
Key takeaways:
- The untyped variable is the string. The
VAR+=1appends, not adds. The arithmetic requires the$(( ))or thedeclare -i. - The
declare -ievaluates the arithmetic. TheCOUNT+=1becomes the6. The invalid value becomes the0. The integer attribute is the sticky. - The
readonlyprevents the reassignment. Theunsetalso fails. The read-only is the constant for the session. Thedeclare -ris the same. - The
exportpasses the variable to the child. The unexported is the invisible. Thedeclare -xis the combined declaration and export. - The arrays are the indexed (
-a) and the associative (-A). The${arr[@]}is all the elements. The${!arr[@]}is the keys. The+=appends.
Remember: The declare is the type. The readonly is the constant. The export is the environment. The local is the scope. The array is the collection. The exam tests the variable semantics, and the explicit type prevents the string concatenation bugs.
Stop using slow, ad-bloated tool sites! ๐คฎ
๐ Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
โ
Finance (Mortgage, Interest, Inflation)
โ
Tech (Base64, JSON, Dev Suite, IP)
โ
Health (BMI, BMR, TDEE)
โ
Productivity (Timer, Workspace, QR)
โก๏ธ Fast & Private
๐ No data leaves your device
๐ 100% Free
๐ Use it now: https://tools.kandz.me
๐ Bookmark itโyouโll need it later!