Skip to content
CogniSkill

LESSON 1 OF 4

Predict what the shell passes to a command

Practice notesBooks for further learning

Start with an argument, not with a command name

A shell command receives an argument list. Before that command runs, Bash processes the words you typed. Variable substitution, word splitting and filename expansion can change that list. Many automation mistakes happen before the intended program sees anything.

Consider a directory called input with spaces. The variable contains a single path string, but an unquoted expansion can split it into three arguments. A command expecting one directory now receives something else. Quoting is part of the interface contract: "$input" asks Bash to pass the expanded value as one argument. It is not cosmetic punctuation.

Run this small inspection in your practice terminal:

input='input with spaces'
printf '<%s>\n' $input
printf '<%s>\n' "$input"

The first command prints three lines: <input>, <with> and <spaces>. The second prints one: <input with spaces>. printf reuses the format for additional arguments; this makes the splitting visible without needing a real collector. Predict the output before running it. If your shell has unusual global settings, record them rather than assuming the course's default context.

Single quotes prevent expansion of the enclosed text; double quotes preserve an argument while allowing selected expansions such as $input. To print the literal string $input, use printf '%s\n' '$input'. To print its current value, use printf '%s\n' "$input". The format is constant and the data is a separate argument. Do not let untrusted text become a printf format or a command to evaluate.

A filename is data, even when it looks strange

Spaces are only the first case. Names can contain tabs, newlines, brackets or leading dashes. A filename resembling an option can affect a command if you do not respect its argument boundary. Many GNU utilities recognize -- as the end of options; rm -- "$path" treats the following argument as a path rather than an option. That is useful syntax, but it does not authorize deletion or make a broad target safe.

The inventory will not delete input. Its only cleanup target is the exact temporary report created by mktemp. This matters more than adding defensive punctuation to a dangerous deletion command. Determine side effects first, then implement the argument contract.

Avoid for file in $(ls ...). That approach turns display text back into shell words. It loses filename boundaries and can quietly change the population. A better small-directory interface is a Bash array expanded as "${files[@]}": each element remains one argument even if it contains whitespace.

names=('alpha.txt' 'two words.txt' '-n.txt')
printf 'items=%s\n' "${#names[@]}"
printf '<%s>\n' "${names[@]}"

Expect items=3 followed by one line per name. Contrast this with "${names[*]}", which joins the elements into one argument using a separator. Neither expansion is universally better; they answer different interface questions. The loop needs the separate-element form.

Matching is a population decision

The expression "$input"/*.txt keeps the directory portion literal but lets *.txt expand. Quoting the entire expression as "$input/*.txt" would suppress that expansion and pass a literal asterisk. Leaving the directory variable unquoted reintroduces splitting.

By default, an unmatched glob can remain as a literal word. An empty input directory might therefore produce a loop iteration over /path/*.txt instead of zero iterations. Our script enables nullglob: no matches produce no array elements. Empty is a valid observation under this small lab contract, not an error and not a fabricated filename.

Do not generalize empty to successful collection in every system. A real export might be empty because access failed or the wrong directory was selected. Our report states the directory scope and only claims what this invocation observed. The later handoff exercise requires you to ask whether that directory is an authoritative source for the business question.

Exercise: predict five argument lists

For each case, say how many arguments follow the format and whether matching happens: unquoted $input; "$input"; "$input"/*.txt; "$input/*.txt"; "${names[@]}". Test with two visible .txt files, one hidden .txt, one nested directory and one file with another extension.

Worked reasoning

With the supplied space-containing value, unquoted expansion produces three words in the default context. The quoted value produces one. The partially quoted glob produces one element per visible top-level match, while the fully quoted pattern produces one literal pattern. The quoted array expansion preserves three elements. Hidden and nested files are outside the glob’s population; that is an explicit scope, not evidence that those files do not exist.

Reasoning model

A shell expands unquoted variables through word splitting and pathname expansion, so a filename containing spaces or wildcard characters can change argument structure. Preserve argument boundaries with quoting and appropriate arrays or null-delimited traversal. A script’s exit behavior needs deliberate design: pipeline status and error-handling options have context-dependent rules. Test failure paths instead of assuming a single shell option makes every operation safe.

Official reference for further study

YOUR NEXT STEP

Keep building your skills

No directly related CogniSkill book is mapped to this course yet. Continue with the free learning, or compare the publishing catalogue by your next goal.

Explore books by topic and goal →

Sign in to save lesson completion. Reading position is separate from completion.

Practice notes — your working space

Save predictions, outputs and explanations as you work. Notes stay on this browser, not your account; people sharing this browser can access them. Avoid passwords and sensitive data. Export to keep a portable copy, or clear your notes before leaving a shared device.

How was this lesson?

Help us improve this lesson.