Bash / Editorial solution guide
Diagnose unexpected argument splitting and globbing
Trace a fictional command wrapper from received arguments back through scalar expansion, IFS, filename matching, and the called program’s option parser.
Read-only knowledge base · No public comments
Recognize the symptoms
A wrapper works for simple labels but fails when a label contains spaces, becomes empty, starts with a dash, or includes an asterisk. The called function sees a different argument count, and adding quote characters inside the stored value does not repair it. Use harmless synthetic strings to reproduce the boundary failure.
Understand the likely causes
- Unquoted ordinary expansion can split using IFS and then match filenames. Empty expansions can disappear. A scalar that joins several intended arguments cannot preserve arbitrary boundaries. Even after quoting is corrected, a leading dash may still be interpreted by the called program as an option; that is a separate interface issue.
1. Observe arguments at the receiving boundary
Record the exact invocation and identify the first function or program receiving the wrong data. Replace real effects with a small inspection function that prints argument count and one clearly delimited value per argument. A decorative echo of the command line cannot reconstruct boundaries and may conceal an empty argument. Keep tracing narrow because expanded values can contain credentials. Confirm the interpreter is Bash before using array-based probes, and reproduce inside an isolated shell rather than editing a production wrapper while it is serving work.
inspect() { printf 'argc=%s\n' "$#"; printf '<%s>\n' "$@"; }
value='red apple'
inspect "$value" '' '*'
argc=3
<red apple>
<>
<*>
The receiver gets three values, including an empty value and a literal wildcard. No file expansion is requested because the data arguments are quoted.
2. Trace the first unquoted ordinary expansion
Follow the value from assignment through each helper. Locate an unquoted $value, $@, or array expansion rather than assuming the final external tool caused the corruption. Check whether IFS was changed by a library and whether a wildcard was intended as data or an actual filesystem query. Quotes embedded inside a variable are not source-level quote syntax and will not group fields retroactively. If one value should remain one argument, use a quoted scalar; if several values must remain separate, represent them as a list from the start.
3. Replace joined strings with a real argument list
In Bash, use an indexed array with one element per intended argument and invoke the fixed command through quoted @ expansion. Do not feed a reconstructed string to eval or another shell parser. Keep command selection constrained and preserve empty elements deliberately. If the script must target POSIX sh, redesign using positional parameters or another documented representation instead of inserting Bash arrays under a generic sh shebang. Review the whole forwarding chain: correcting one helper does not fix a later unquoted expansion.
receive() { [[ $# == 3 && $1 == 'red apple' && -z $2 && $3 == '*' ]]; }
args=('red apple' '' '*')
if receive "${args[@]}"; then printf 'PASS exact list\n'; else exit 1; fi
PASS exact list
The receiving assertion tests observable values rather than a printed command string. A joined or unquoted replacement would fail this exact contract.
4. Review the next parser after quoting
If the value now arrives intact but still changes behavior, inspect the utility’s option and format rules. A quoted -n remains one argument and can still be an option. Use a documented -- separator where supported or an appropriate explicit pathname. printf needs a fixed format with data in %s, not a quoted external format string. Quoting preserves boundaries; it does not validate paths, authorize actions, or neutralize every downstream language. Apply the next layer’s own input contract without weakening the repaired shell boundary.
Verify the fix
Run the receiving-boundary test with ordinary text, spaces, an empty value, a literal wildcard, a leading dash, and a newline if the interface permits it. Assert exact counts and values, then separately check the called program’s documented option behavior. Verify stdout and status, not only a visually plausible log line.
Keep it from recurring
- Use quoted scalar expansions and quoted array forwarding consistently, keep list construction separate from command selection, and retain the edge-value regression. Scope IFS and glob-option changes inside helpers. Document whether patterns are data or intentional pathname queries, and reject unsupported representations instead of adding another layer of evaluation.