Bash / Basic: syntax, values, and control / Lesson 3
Design parameter and string contracts
Use positional parameters, defaults, substring operations, and explicit value validation while preserving unset and empty distinctions.
On this page
Lesson goals
- Explain named, positional, and special parameters.
- Distinguish defaulting, assignment, and required-value expansion.
- Use parameter operations without treating data as arithmetic code.
Parameters have different roles
Named variables hold shell state, positional parameters hold an invocation’s arguments, and special parameters expose information such as argument count and recent status. ${10} selects the tenth positional parameter; $10 is parsed differently. "$@" preserves the positional list and is normally the right forwarding form. Assignment syntax has no spaces around equals. Export affects which named values reach later child environments, not whether the shell itself can read them. State whether a function or script accepts missing values, empty strings, arbitrary text, or a constrained identifier before choosing expansion syntax.
The colon changes the missing-value test
${name-default} substitutes only when name is unset, while ${name:-default} also substitutes for an empty value. Related forms can assign a default or reject missing input with a diagnostic. Those operations change behavior, so do not choose them merely because they look concise. A harmless optional display label may accept a fallback; an output path may require explicit validation instead. With nounset enabled, deliberately guarded expansions can support optional parameters, but the option is not a complete input schema and does not establish that a nonempty value is safe.
unset label
printf 'unset=<%s>\n' "${label-fallback}"
label=''
printf 'empty-no-colon=<%s>\n' "${label-fallback}"
printf 'empty-colon=<%s>\n' "${label:-fallback}"
unset=<fallback>
empty-no-colon=<>
empty-colon=<fallback>
Only the colon form treats the explicitly empty value as a reason to use fallback. None of these expansions assigns a new value to label.
String removal uses patterns rather than regular expressions
Bash parameter operations can remove a shortest or longest matching prefix or suffix and perform substitutions. Their pattern language follows shell matching rules, not the regular-expression syntax of =~. A path suffix operation is a string transformation, not a filesystem lookup or security validation. Multiple dots, absent separators, and empty inputs deserve explicit tests. Some transformations, including case conversion, are Bash extensions and can depend on locale. Preserve quoting around the resulting value, because the transformation does not automatically protect an unquoted result from later expansion stages.
name='lab.report.txt'
printf 'short=<%s> long=<%s>\n' "${name%.*}" "${name%%.*}"
printf 'suffix=<%s>\n' "${name##*.}"
short=<lab.report> long=<lab>
suffix=<txt>
The same pattern has different reach with one or two percent signs. These operations inspect the string only and create or open no file.
Validate numeric text before arithmetic
Arithmetic contexts interpret expressions, not merely decimal strings. Variable references and array subscripts can therefore behave differently from ordinary quoted data. Decide a numeric grammar, range, and base before evaluating external input. Leading zero notation has special meaning in shell arithmetic, so an apparently decimal identifier should not silently become another base. Do not evaluate arbitrary user text to determine whether it is a number. Use a lexical check first and handle range and overflow requirements deliberately; Bash integer arithmetic is not an arbitrary-precision or financial decimal engine.
Keep parameter contracts visible at boundaries
A script should validate arguments once near entry, then pass normalized values to small functions. Avoid surprising global defaults that hide a caller mistake. Capture a command’s status before expanding unrelated diagnostics, and document whether a helper returns text or modifies state. A variable name received from outside is not ordinary harmless data when used for indirect assignment; advanced lessons examine that boundary. Store examples for empty, unset, spaced, dash-prefixed, and nonnumeric values so maintenance changes preserve the original contract instead of only the common happy path.
Common errors and fixes
A required empty value silently defaults
- Why it happens
- The colon form was chosen without an empty-value policy.
- How to fix it
- Distinguish unset and empty in validation.
- Prevent it next time
- Write the input contract before defaults.
A glob pattern is read as a regular expression
- Why it happens
- Parameter matching and =~ were conflated.
- How to fix it
- Use the correct pattern language and test edge cases.
- Prevent it next time
- Name the matching mechanism in helper documentation.
External numeric text is evaluated directly
- Why it happens
- Arithmetic expression parsing was assumed to be decimal validation.
- How to fix it
- Validate grammar and range before arithmetic.
- Prevent it next time
- Keep identifiers and executable expressions separate.
Check your understanding
1. Choose the expansion when empty is valid but unset needs a display fallback.
Show worked solution
Use ${label-fallback}, with quoting at the use site. The non-colon form preserves an explicitly empty value. Validate a required non-display value separately rather than borrowing the same fallback policy for every input.
2. For a.b.c, compare ${value%.*} and ${value%%.*}.
Show worked solution
The shortest matching suffix removal leaves a.b, while the longest leaves a. These are pattern-based string operations. They do not prove either remaining string names an existing file or a permitted output location.
Key takeaways
- Choose unset, empty, pattern, and numeric semantics explicitly.
- Validate at entry and preserve quoted boundaries afterward.