Bash & the POSIX Shell Module 1 – Start here: the shell, the terminal and your first script
Commands, options, arguments and help
Split a command line into its name, options, option-arguments and operands, end options with --, and find help with type, help, man and --help.
What you will learn
- Split a command line into its command name, options, option-arguments and operands
- Use -- to end options and handle file names that start with a dash
- Find help with type, help, man and --help, and tell keywords, builtins, functions, aliases and files apart
Before you start
What you’ll learn · Before you start
What you will learn
- Split a command line into its command name, options, option-arguments and operands
- Use -- to end options and handle file names that start with a dash
- Find help with type, help, man and --help, and tell keywords, builtins, functions, aliases and files apart
Before you start
On this page
In a folder that holds a file called -n, the command cat * prints one numbered line instead of the contents of
both files, and nothing warns you: cat read the file’s name as its “number the lines” option. Knowing how a command
line splits into a name, options and operands is what lets you see that coming.
Below you will take command lines apart into their pieces, watch a glob turn a file name into an option and fix it with
--, follow the order in which Bash decides whether a name is an alias, a keyword, a function, a builtin or a program,
and find the right help for each kind of command.
A command line is a name, options, option-arguments and operands
Take head -n 2 drinks.txt. The first word, head, is the command name. -n is an option: A word that starts with a dash and changes how a command behaves, such as -n for head. One-letter options without values can usually be grouped behind one dash, as in cat -ne.,
2 is its option-argument, the value the option needs, and drinks.txt is an operand: A word after a command's options that the command works on, usually a file name. After --, every word is an operand, even one that starts with a dash.,
the thing the command works on. POSIX.1-2024’s utility syntax guidelines describe this shape for standard utilities,
and most other programs follow it.
| Part | In head -n 2 drinks.txt |
In cat -ne drinks.txt |
|---|---|---|
| Command name | head |
cat |
| Options | -n |
-n and -e, grouped |
| Option-argument | 2 (belongs to -n) |
none |
| Operands | drinks.txt |
drinks.txt |
The guidelines also allow one-letter options without arguments to be grouped behind a single dash, so cat -ne means
cat -n -e. Here are both forms on a three-line file, with -n numbering the lines and -e marking each line’s end
with $:
from session import run
# One small file, then the same options written apart and grouped behind one dash.
run("""
printf 'tea\\ncoffee\\nwater\\n' > drinks.txt
head -n 2 drinks.txt
cat -n -e drinks.txt
cat -ne drinks.txt
""") Output
$ printf 'tea\ncoffee\nwater\n' > drinks.txt
$ head -n 2 drinks.txt
tea
coffee
$ cat -n -e drinks.txt
1 tea$
2 coffee$
3 water$
$ cat -ne drinks.txt
1 tea$
2 coffee$
3 water$
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 anatomy_session.py
The two cat commands print exactly the same lines, because grouping changes only how the options are written.
Many programs also accept long options, whole words after two dashes such as --help or --color=never. They are a
convention of GNU and other projects rather than one of the POSIX guidelines, so a long option that works on Linux may
be missing from the macOS version of the same command, as the last section of this lesson shows with ls --help. When
a script must run on both, prefer the short options that POSIX describes for the utility.
Reading an option-argument as an operand
Wrong: “head -n 2 drinks.txt works on two files, 2 and drinks.txt”. Right: 2 belongs to -n, so only
drinks.txt is an operand. A command that takes an option-argument reads the next word for it, whatever it looks
like.
Two dashes end the options, so a file named like an option stays a file
The shell expands * into the names of the files in the folder before the command starts, and the command cannot tell
a word that came from a file name from one you typed. POSIX guideline 10 gives the cure: the first -- that is not an option-argument marks the end of the options, and every word after it is an
operand, even one that begins with a dash. Watch a file called -n change what cat * does:
from session import run
# A file whose name starts with a dash, and a glob that hands that name to cat.
run("""
printf 'hello\\n' > -n
printf 'world\\n' > notes.txt
ls
cat *
cat -- *
cat ./*
rm -- -n
ls
""") Output
$ printf 'hello\n' > -n
$ printf 'world\n' > notes.txt
$ ls
-n
notes.txt
$ cat *
1 world
$ cat -- *
hello
world
$ cat ./*
hello
world
$ rm -- -n
$ ls
notes.txt
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 dash_file_session.py
cat * became cat -n notes.txt, so cat numbered the one file it was given and never printed “hello”. Both fixes
work: cat -- * ends the options before the file names, and cat ./* makes every name start with ./, which is not a
dash. Even removing the file needs the same care, which is why the session uses rm -- -n. ShellCheck warns about a
bare * in arguments for exactly this reason (SC2035).
Key idea
A glob hands file names to a command as ordinary words, so write -- * or ./* whenever a name could start with a
dash.
Checkpoint
Check: Name, options and operands · Ending options with --
The type builtin shows what a name means, and command tells whether it exists
The same word can mean several things. if is a keyword of the shell’s language, echo is a builtin: A command that is part of the shell itself rather than a separate program, such as cd, echo or type in Bash. help NAME documents it. compiled
into Bash and also a program in /bin, ls is only a program, and you can make a name into a function or an alias
yourself. Bash’s reference manual gives the order in which it decides: aliases and keywords are recognised while the
line is read, then come functions, then builtins, then a search of the folders in PATH, remembered in a hash table.
Follow one name down that order:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- While the line is read. Before anything runs, Bash checks the first word against its keywords, such as if, and its aliases, such as ll. An alias is replaced by its text, so ll becomes ls -l.
- Functions come first. A name that is not an alias or keyword is looked up as a function. The greet function defined in the terminal wins over any program of the same name.
- Then builtins. Next come the builtins compiled into Bash, such as echo, printf and cd. That is why type -a lists echo as a shell builtin before the program /bin/echo.
- Then the hash table. For a program, Bash first looks in its hash table of full paths it found earlier, so it need not search PATH every time. hash -r empties it.
- Then every folder in PATH. Otherwise Bash searches the folders of PATH in order and runs the first executable file with that name, such as /bin/ls.
- Nothing matched. When no stage recognises the name, Bash prints "command not found" and the command's exit status is 127, which scripts can test.
type -a NAME prints every meaning a name has, in that order, and command -v NAME prints how the name would run, or
nothing with status 1 when it does not exist. Here they are after a function and an alias have been defined:
from session import run
# What kind of command is each name? A function and an alias are defined first, as you might in a terminal.
run("""
greet() { echo "hi, $1"; }
alias ll='ls -l'
type -a echo printf ls
type if greet ll
command -v ls
!command -v nosuchcmd
command -v nosuchcmd || echo missing
""") Output
$ greet() { echo "hi, $1"; }
$ alias ll='ls -l'
$ type -a echo printf ls
echo is a shell builtin
echo is /bin/echo
printf is a shell builtin
printf is /usr/bin/printf
ls is /bin/ls
$ type if greet ll
if is a shell keyword
greet is a function
greet ()
{
echo "hi, $1"
}
ll is aliased to `ls -l'
$ command -v ls
/bin/ls
$ command -v nosuchcmd
[exit status 1]
$ command -v nosuchcmd || echo missing
missing
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 lookup_session.py
type -a found echo twice, as a builtin and as /bin/echo; typing echo runs the builtin because builtins come
first. For nosuchcmd, command -v printed nothing and ended with status 1, which is why command -v nosuchcmd || echo missing printed “missing”. In a script, command -v is the portable way to ask whether a command exists, because
POSIX defines it for every shell.
PATH itself is a list of folders separated by colons, searched from left to right, and the first folder that holds an
executable file with the name wins. Bash remembers each program it finds in its hash table, so after you install a
new copy of a program earlier in PATH, an open terminal may keep running the old one until you type hash -r or open
a new terminal. type tells you when that happens: it answers “ls is hashed (/bin/ls)” for a remembered name.
Using which to test for a command in a script
Wrong: if which jq in a script. Right: if command -v jq >/dev/null. which is a separate program that POSIX does
not define, and its output and exit status differ between systems, while command -v behaves the same in every
POSIX shell.
help, man and the help option each document a different kind of command
Where you find documentation depends on what kind of command you have. Builtins are documented inside Bash, by the
help builtin. Programs installed on the system usually have a manual page, which man shows and man -w locates.
Many programs, including Bash’s builtins from Bash 4.4 on, also answer --help with a short usage line, but not all of
them: macOS’s own ls rejects the option.
from session import run
# Three ways to read about a command: help for builtins, --help for many programs, man for installed manual pages.
run("""
help type | head -n 2
printf --help 2>&1 | head -n 1
!ls --help
man -w ls
""") Output
$ help type | head -n 2
type: type [-afptP] name [name ...]
Display information about command type.
$ printf --help 2>&1 | head -n 1
printf: printf [-v var] format [arguments]
$ ls --help
ls: unrecognized option `--help'
usage: ls [-@ABCFGHILOPRSTUWXabcdefghiklmnopqrstuvwxy1%,] [--color=when] [-D format] [file ...]
[exit status 1]
$ man -w ls
/usr/share/man/man1/ls.1
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 help_session.py
| Kind of command | Where its documentation is | Example |
|---|---|---|
| Keyword or builtin | help NAME |
help type, help cd |
| Installed program | man NAME |
man ls |
| Many programs and Bash builtins | NAME --help |
printf --help in Bash |
| The shell itself | man bash |
every keyword, builtin and expansion |
Run type -a first when you are unsure: if it says “shell builtin”, use help; if it prints a path, use man.
One habit for an unfamiliar command
Ask type -a NAME, then read the matching help: help for a builtin, man for a program. Try --help last, and
read its error if the program does not support it.
Checkpoint
Check: What a name means to Bash · Where to find help
Practise: read command lines and find their help
Each question gives a command line or a name and asks what Bash makes of it; the wrong answers come from the mistakes above, and each explains itself:
Practice that adapts to you
Practise this lesson
9 questions about 4 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up (fresher to 2–5 years): what do -- and command -v do in a script?
-- marks the end of a command’s options: every word after it is an operand, even one that starts with a dash. That
protects commands such as rm -- "$file" or cat -- * from file names like -n or -rf, which would otherwise be
read as options. command -v NAME prints how the shell would run NAME, a path for a program, the bare name for a
builtin or function, or the definition for an alias, and returns status 1 with no output when NAME does not exist. Because POSIX defines it, it is the
portable test for “is this command installed?” in a script: if command -v jq >/dev/null; then …. It is preferred to
which, a separate program that POSIX does not define.
Two harder questions follow, about a dangerous glob and about a command that runs the wrong program. Work out your answer before you open each first step:
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher to senior
A nightly script runs cd /srv/uploads; rm * to empty a folder that users can write to. What can go wrong, and how would you rewrite the line?
-
Mid to senior
On one machine, typing grep runs an old version with different options, although you installed a new one. How do you find out what grep means to your shell, and how do you fix it?
Flashcards · 6 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
Into which parts does the command line head -n 5 notes.txt split?
The command name head, the option -n, its option-argument 5, and the operand notes.txt, which is what the command works on.
-
What does -- do in rm -- "$file"?
It ends the options. Every word after it is an operand, even one that begins with a dash, so a file called -rf is removed as a file and never read as options.
-
Why can rm * go wrong in a folder that holds a file named -n or -rf?
The glob hands each name to rm as an ordinary word, so a name starting with a dash is taken as an option. Write rm -- * or rm ./* so every name stays a file.
-
In which order does Bash resolve a command name?
Aliases and keywords first, then functions, then builtins, and last the programs it finds through PATH. type -a NAME lists every match in that order.
-
What is the portable test for whether a command exists?
command -v NAME. It prints how the shell would run the name: a path for a program, the bare name for a builtin or function, the definition for an alias. When nothing matches it prints nothing and ends with status 1.
-
Where do you look up a builtin, an installed program and a tool's own options?
help NAME documents Bash builtins, man NAME opens the manual page of an installed program, and NAME --help prints the option list that many tools carry themselves.
Key takeaways
- A command line splits into the command name, options, their option-arguments and operands, and one-letter options can be grouped behind one dash.
- A glob passes file names as ordinary words, so
--or./*keeps a name such as-nfrom becoming an option. - Bash resolves a name as an alias or keyword first, then as a function, a builtin and finally a program found through
PATH, andtype -alists every match in that order. command -vis the portable test for whether a command exists, andhelp,manand--helpdocument builtins, installed programs and many tools respectively.
Terms in this lesson
- builtin
- A command that is part of the shell itself rather than a separate program, such as cd, echo or type in Bash. help NAME documents it.
- operand
- A word after a command's options that the command works on, usually a file name. After --, every word is an operand, even one that starts with a dash.
- option
- A word that starts with a dash and changes how a command behaves, such as -n for head. One-letter options without values can usually be grouped behind one dash, as in cat -ne.
References
- POSIX.1-2024: Utility Conventions (12.2 Utility Syntax Guidelines) (The Open Group and IEEE)
- Bash Reference Manual: Command Search and Execution (Chet Ramey (GNU Bash maintainer))
- POSIX.1-2024: command (The Open Group and IEEE)
- SC2035: Use ./*glob* or -- *glob* so names with dashes will not become options (ShellCheck)
Report a problem with this lesson
Kept only in this browser. Your Learn progress