Linux Basic — String Length & Empty Checks

3 minute read

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

Updated: