Linux Basic — String Length & Empty Checks
String Length & Empty Checks
How long is this string? Is it empty? Is it unset? Three different questions with three different answers — and parameter expansion has a clean operator for each.
Length — ${#var}
name="Alice"
echo "${#name}"
# 5
empty=""
echo "${#empty}"
# 0
unset missing
echo "${#missing}"
# 0 (unset behaves like empty for length)
${#var} returns the number of bytes, not characters. For ASCII they’re the same. For multi-byte UTF-8, count differs:
greet="café"
echo "${#greet}"
# 5 ← 4 visible chars, but é is 2 bytes in UTF-8
If you need codepoint counts, fall back to a tool:
echo -n "café" | wc -m
# 4 (wc -m counts characters in current locale)
For arrays, ${#arr[@]} returns the element count, and ${#arr[i]} the byte length of element i:
arr=(one two three)
echo "${#arr[@]}" # 3 (count)
echo "${#arr[2]}" # 5 (length of "three")
Empty vs Unset — -z and -n
-z (“zero length”) and -n (“non-zero length”) test the expansion, so unset and empty behave the same:
[[ -z "$name" ]] && echo empty
[[ -n "$name" ]] && echo "has content"
unset x
[[ -z "$x" ]] && echo "x is empty/unset"
Important: under set -u (covered in Handling Errors), referencing $x when x is unset is a fatal error. Use the :- default to make the check safe:
set -u
unset x
[[ -z "$x" ]] && echo a # unbound variable — script exits
[[ -z "${x:-}" ]] && echo a # safe — expands to empty if unset
${x:-} says “if x is unset or empty, substitute empty.” It’s the recommended idiom in strict-mode scripts.
Parameter Defaults — :-, :=, :?, :+
Four operators for “what to do when this variable isn’t set (or is empty)”:
| Form | If var is set & non-empty |
If var is unset or empty |
|---|---|---|
${var:-DEFAULT} |
$var |
DEFAULT (does NOT set var) |
${var:=DEFAULT} |
$var |
DEFAULT and sets var |
${var:?MESSAGE} |
$var |
print MESSAGE to stderr, exit |
${var:+ALT} |
ALT |
"" |
(Drop the : for “unset” only — empty strings then count as set.)
:- — Substitute a Default
greeting=${GREETING:-Hello}
echo "$greeting"
# Hello (because GREETING was unset)
GREETING="Hi"
echo "${GREETING:-Hello}"
# Hi
Most common use: command-line argument defaults.
log_dir=${1:-/var/log}
echo "Using log dir: $log_dir"
:= — Substitute AND Assign
unset NAME
echo "${NAME:=World}" # World
echo "$NAME" # World — NAME is now set
Note: := doesn’t work on positional parameters ($1, $2). Use :- and assign manually.
:? — Require It Or Die
HOST="${HOST:?HOST is required}"
# bash: HOST: HOST is required (and script exits)
Perfect for env-var contracts at the top of a script:
: "${API_TOKEN:?API_TOKEN must be set}"
: "${REGION:?REGION must be set}"
The leading : is a no-op command — it lets you “run” the expansion purely for its side effects.
:+ — Use Alternative Only If Set
flag=${VERBOSE:+--verbose}
mycmd "$flag" file.txt
If VERBOSE is set, flag becomes --verbose; otherwise it’s empty and the command runs without the flag. Cleaner than an if.
The : Difference Made Plain
unset x; echo "[${x-default}]" # [default] x is unset
x="" ; echo "[${x-default}]" # [] x is set (just empty) — no substitution!
x="" ; echo "[${x:-default}]" # [default] ':' treats empty as unset
Rule of thumb: Use the :- form almost always. Drop the colon only when you specifically need to distinguish “unset” from “empty”.
Sanity Patterns
# CLI arg with default
out_dir=${1:-./build}
# Required env var
: "${DATABASE_URL:?DATABASE_URL is required}"
# Conditional flag pass-through
debug_flag=${DEBUG:+--debug}
# Read with fallback prompt
read -p "Name [Anonymous]: " name
name=${name:-Anonymous}
Common Mistakes
| ❌ Wrong | ✅ Right | Issue |
|---|---|---|
[ -z $x ] (unquoted) |
[[ -z $x ]] or [ -z "$x" ] |
empty $x makes [ -z ] malformed |
[ -z "${x}" ] under set -u with unset x |
[ -z "${x:-}" ] |
strict mode bails on unset |
${x:=val} on $1 |
x=${1:-val} |
:= doesn’t work on positional params |
len=$(echo -n "$s" \| wc -c) |
len=${#s} |
needless fork |
${var-default} when empty means “use default” |
${var:-default} |
empty stays empty without the colon |
Next → Substring Extraction