Skip to content

Use Bash Parameter Expansion for Defaults, Validation, and String Processing

In Bash, many variable-value transformations can be performed with parameter expansion alone, without starting external commands such as sed, cut, or basename.

Setting defaults, validating required values, removing prefixes and suffixes, and replacing strings are especially common even in short scripts. Using Bash built-in syntax for these operations can make the intent clearer while reducing unnecessary external processes.

This article checks each expansion independently and then combines them into a single input-processing flow.

Step 1: Set a default for an unset or empty value

Section titled “Step 1: Set a default for an unset or empty value”

${parameter:-word} returns word when parameter is unset or an empty string. If the original variable must be assigned, assign the result or use ${parameter:=word}.

The following example applies production as the default both when the variable is unset and when it is empty.

Terminal window
unset APP_MODE
APP_MODE=${APP_MODE:-production}
[[ $APP_MODE == production ]] || {
printf 'default check failed\n' >&2
exit 1
}
APP_MODE=
APP_MODE=${APP_MODE:-production}
[[ $APP_MODE == production ]] || {
printf 'empty-value default check failed\n' >&2
exit 1
}
printf 'default handling: ok\n'

If an empty string is a valid value and the default should be used only when the variable is unset, use ${parameter-word}. The presence or absence of the colon changes how an empty string is treated, so choose the form that matches the input specification.

Step 2: Validate required and invalid values

Section titled “Step 2: Validate required and invalid values”

${parameter:?message} writes message to standard error and makes the shell operation fail when the variable is unset or empty. It is useful when a required input must not be silently replaced with a default.

However, this expansion checks only whether a value exists. To validate content such as a file extension or an allowed value, combine it with Bash constructs such as [[ ... ]] or case.

The following example first detects an unset variable as a required-value error and then detects a value other than .txt as invalid.

Terminal window
unset INPUT_PATH
missing_detected=false
if (
: "${INPUT_PATH:?INPUT_PATH is required}"
); then
printf 'missing-value check failed\n' >&2
exit 1
else
missing_detected=true
fi
INPUT_PATH='notes.csv'
invalid_detected=false
if [[ $INPUT_PATH == *.txt ]]; then
printf 'invalid-value check failed\n' >&2
exit 1
else
invalid_detected=true
fi
[[ $missing_detected == true && $invalid_detected == true ]] || exit 1
printf 'required-value validation: ok\n'

In an actual script, first check for the presence of a required value with ${parameter:?message}, then add validation for allowed values or formats as needed. Keeping these checks separate makes input conditions easier to reason about.

${parameter#pattern} removes the shortest matching pattern from the beginning, while ${parameter##pattern} removes the longest match. At the end of the value, use ${parameter%pattern} and ${parameter%%pattern}.

The following example removes a fixed directory portion as a prefix and then removes .gz as a suffix.

Terminal window
path='/srv/example/archive.tar.gz'
without_prefix=${path#/srv/example/}
without_suffix=${without_prefix%.gz}
[[ $without_prefix == archive.tar.gz ]] || {
printf 'prefix trimming failed\n' >&2
exit 1
}
[[ $without_suffix == archive.tar ]] || {
printf 'suffix trimming failed\n' >&2
exit 1
}
printf 'prefix/suffix trimming: ok\n'

pattern supports Bash pattern matching. For example, ${name##*/} can remove everything through the last /. With complex input formats, verify which portion will be considered the shortest or longest match before relying on it.

${parameter/pattern/replacement} replaces the first matching portion. To replace every match, use ${parameter//pattern/replacement}.

The following example replaces the URL scheme and then the version string in the path.

Terminal window
endpoint='https://api.example.com/v1/items'
rewritten=${endpoint/https/http}
rewritten=${rewritten/v1/v2}
[[ $rewritten == 'http://api.example.com/v2/items' ]] || {
printf 'replacement failed\n' >&2
exit 1
}
printf 'string replacement: ok\n'

Here too, pattern is a Bash pattern, not a regular expression. Do not write it under the assumption that regular-expression syntax will be interpreted.

Step 5: Build input processing with parameter expansion alone

Section titled “Step 5: Build input processing with parameter expansion alone”

Finally, combine default assignment, required-value checking, prefix removal, suffix removal, and string replacement into one flow. Because printf, [[ ... ]], assignment, and parameter expansion are all handled by Bash itself, no external command is needed for the string transformation.

Terminal window
unset RAW_NAME
RAW_NAME=${RAW_NAME:-prefix-example.txt}
checked=${RAW_NAME:?RAW_NAME is required}
name=${checked#prefix-}
name=${name%.txt}
name=${name/example/sample}
[[ $name == sample ]] || {
printf 'parameter-expansion pipeline failed\n' >&2
exit 1
}
printf 'parameter-expansion pipeline: ok\n'

Parameter expansion is useful for expressing short string transformations without external commands. However, ${parameter:?message} alone does not validate the meaning or format of a value. Separating presence checks from content validation with [[ ... ]] or case helps keep input processing readable.

When an expanded value is passed as a command argument, normally quote it as "$variable" to avoid unintended word splitting or pathname expansion.

Category: Linux