Module 1: The Terminal and the File System

4. The anatomy of a command: flags and arguments

Description

By the end of this lesson you will be able to look at any command — even one you have never seen before, copied from a tutorial, a forum answer, or suggested by a language model — and split it into its three pieces: what program is going to run, what modifiers you are passing it, and what data it is going to act on. That split is what lets you predict what is going to happen before you press Enter, instead of finding out the hard way.

This matters in real work every day: you are going to copy commands from a tool's documentation, from a teammate, or from a continuous integration script, and you are going to have to decide in seconds whether it is safe to run as-is or whether you need to adjust something first. A command with rm, with sudo, or with a misunderstood redirection can delete or overwrite something it should not have. Understanding its anatomy is the first safety filter — even before knowing how to read its manual, which is exactly the topic of the next lesson.

Connection to the module: the previous lesson left you with a terminal open and working on your operating system. This one gives you the grammar to read anything you type into it. Without that grammar, every new command is a hieroglyph; with it, it is a sentence with a recognizable structure.

The universal grammar of a command

Think about how you would give someone an instruction in a kitchen: "cut the bread, thin, with the big knife." There are three distinct pieces of information there: the action (cut), a modifier of how to do that action (thin), and the object it acts on (the bread). If you change the modifier — "cut the bread, thick" — the action stays the same, but the result changes. A terminal command is built exactly the same way.

Everything you type into a terminal follows this same three-piece structure:

  • Command: the name of the program you want to run (ls, grep, tar, mkdir…). It always comes first.
  • Options (also called flags): modifiers that change how that command behaves. They always start with one or two dashes.
  • Arguments: the data the command is going to act on — almost always a file, a folder, or a piece of text.

The command always comes first. By convention, options usually come next and arguments come last, although many modern programs tolerate mixing the order of options and arguments without a problem.

Worked example

ls -la /etc

What to expect (real output, trimmed):

total 1240
drwxr-xr-x  87 root root  8192 Jul 18 09:14 .
drwxr-xr-x  20 root root  4096 Jun  2 08:00 ..
-rw-r--r--   1 root root  3028 Jan 10  2026 adduser.conf
-rw-r--r--   1 root root  2761 Mar  5 09:22 bash.bashrc
drwxr-xr-x   3 root root  4096 Jul 18 09:14 cron.d
...

Splitting the command into its three pieces:

  • ls is the command: the program you are trying to run.
  • -la are combined options: -l asks for the long format (one line per file, with permissions, owner, size, and date) and -a asks it to also include hidden files (the ones that start with .).
  • /etc is the argument: the path ls should operate on.

Do not worry yet about decoding every column in that output — that is exactly what the navigation lesson later in this module covers. For now, just keep the shape: command, then options, then argument.

Short and long flags: the same option, two ways to write it

Almost every common option in command-line tools has two ways of being written, and both do exactly the same thing:

grep -i "root" /etc/passwd
grep --ignore-case "root" /etc/passwd
  • Short flag: a single dash followed by a letter (-i). Fast to type, meant for everyday interactive use.
  • Long flag: two dashes followed by a full English word (--ignore-case). Slower to type, but self-descriptive — ideal for a script that someone else (or you, six months from now) is going to have to read without running it.

Cross-platform note: on Linux and inside WSL — which runs real Linux, as you saw in the previous lesson — ls is the GNU coreutils version and understands both -a and its long form --all. On macOS, though, ls is the BSD version and only understands short options: there is no equivalent --all. If you copy a tutorial written for Linux and some long flag throws an error on macOS, it is not that you typed it wrong: they are two different implementations of the same command.

Combining short flags and flags with a value

When several short flags do not need a value, they can be stuck together behind a single dash. That is why -la is the same as -l -a:

tar -xzvf backup.tar.gz
# is exactly the same as:
tar -x -z -v -f backup.tar.gz

Here -x extracts, -z decompresses with gzip, and -v shows each file as it processes it (verbose mode). But -f is different: it needs a value — the file name, backup.tar.gz. That is why -f goes at the end of the combo: the value that follows belongs to that last letter, not to the whole block.

When an option needs a value, there are several valid ways to write it:

head -n 5 notes.txt        # short option, value separated by a space
head -n5 notes.txt         # short option, value stuck together (also valid)
head --lines=5 notes.txt   # long option, value with the = sign
head --lines 5 notes.txt   # long option, value separated by a space

With long options, the = sign is the standard way to join the option to its value with no ambiguity — later you will see why writing it with spaces around the = does not work.

Why spaces separate (and what quotes are for)

Before the program ever receives a single argument, the shell has already chopped the whole line you typed into pieces, using the space as a separator. Each piece becomes a distinct argument. This has a consequence that surprises almost everyone the first time:

mkdir new folder
ls

What to expect:

new  folder

mkdir did not receive a single argument ("new folder"); it received two ("new" and "folder") and created two separate folders, because the shell split the line on the space before mkdir ever saw anything.

For a space to be part of a single argument, you have to tell the shell not to treat it as a separator. Quotes do exactly that:

mkdir "new folder"
ls

What to expect:

new folder

Now it really is a single folder, with a space in the name. The same result comes from escaping the space with a backslash: mkdir new\ folder. Double quotes ("...") and single quotes ('...') work the same for this case; the difference between the two shows up when you use variables inside the text, something you do not need yet.

The -- separator: when an argument looks like an option

What happens if the file you want to delete is named -verbose.log, with a dash at the start? If you type rm -verbose.log, the program tries to interpret that as an option (or a combination of short flags), not as a file name — and it will probably fail with a confusing error or, worse, do something you did not expect.

The -- separator solves this: it tells the program "everything that follows is a positional argument, even if it starts with a dash, not an option anymore."

rm -- -verbose.log

It is a convention almost every command-line program respects, not a trick specific to rm. Worth remembering for the day you run into a strange file name.

command not found: what it exactly means (and what it does not)

When you press Enter, the shell takes the first word on the line and, before running anything, searches for a program with that exact name inside a list of folders stored in the PATH environment variable, in order, stopping at the first match. If none of those folders has a program with that name, the shell never gets to "running" anything: it simply responds with something like this and exits with status code 127:

zsh: command not found: gitt

What it does mean: the shell did not find, in the folders it knows about, a program named exactly that.

What it does not mean:

  • It does not necessarily mean the program is not installed on your machine — it could be installed in a folder that simply is not part of your PATH.
  • It does not mean something is wrong with your arguments — the error happens before the shell even gets to looking at them.
  • It is not the same as No such file or directory, a different error that shows up when the program was in fact found and did run, but one of its arguments (a file or a path) is what does not exist.

Ergonomics that save you hours from day one

A handful of shortcuts you will use in every terminal session, starting today:

  • Up / Down arrows — history: the shell remembers every command you typed during the session (and saves it across sessions in a file like ~/.bash_history or ~/.zsh_history). The up arrow brings back the previous command without having to retype it; the down arrow moves back forward.
  • Tab — autocompletion: start typing the name of a command, file, or folder and press Tab. If there is only one possibility, the shell completes the rest for you; if there are several, pressing Tab twice shows you the options. Saves typing and prevents typos in long names.
  • Ctrl+C — abort: sends an interrupt signal to the process currently running in the foreground and stops it. It is your panic button when a command hangs or you end up in a mode you do not recognize.
  • Ctrl+L — clear screen: clears what is showing on screen (equivalent to the clear command), but does not erase your history or what you already ran, just the visual clutter.
  • Ctrl+A / Ctrl+E — move within the line: Ctrl+A moves the cursor to the beginning of the line you are typing; Ctrl+E moves it to the end. These are shortcuts inherited from the Emacs-style editing mode that bash and zsh use by default, and they work the same on macOS, Linux, and inside WSL, because in all three cases you are facing the same bash or zsh.

Common mistakes

1. Confusing command not found with No such file or directory (conceptual). What happens: you see an error and assume "the command is wrong" when the real problem is an argument, or the other way around. Why it happens: both messages sound similar ("could not find something") but correspond to different phases: command not found is the shell failing to find the program; No such file or directory is the program, already running, failing to find a file or path you passed it as an argument. How to spot it: look at which word appears next to the error — if it is the command's name, it is the first phase; if it is a file name or path, it is the second. How to fix it: if it is the first, check that you typed the program's name correctly and that it is installed; if it is the second, check the path or file name, not the command.

2. Forgetting quotes on an argument with spaces. What happens: you type mkdir new folder expecting one folder and end up with two. Why it happens: the shell chops the line on spaces before the program ever sees anything; for the shell, every space is the end of one argument and the start of the next. How to spot it: run ls afterward and you will see more entries than you expected. How to fix it: wrap the whole argument in quotes (mkdir "new folder") or escape the space with a backslash (mkdir new\ folder).

3. Writing spaces around the = in a long option with a value (conceptual). What happens: you type head --lines = 5 notes.txt thinking it is equivalent to --lines=5, and the program complains about an unknown option or an unexpected argument. Why it happens: the shell already chopped that line on spaces before head ever saw it — it received four pieces (--lines, =, 5, notes.txt), not one option with its value. = with no spaces works because, with no spaces in between, it stays a single piece. How to spot it: the program complains about an option it does not recognize or about extra arguments. How to fix it: no spaces around the = (--lines=5), or use the form separated by a single space with no = (--lines 5).

Exercises

1. Given the command tar -xzvf backup.tar.gz -C /tmp/restore, identify: the command, which flags are combined and which one of them carries a value, and which are the arguments with a value.

See solution

Command: tar.

Combined flags: -xzvf is actually four short flags stuck together — -x (extract), -z (decompress with gzip), -v (verbose mode), and -f (the next value is the file to read). Of those four, only -f needs a value, and that is why it goes last inside the combo.

Separate flag: -C is a different flag, written apart, that also carries a value.

Arguments with a value: backup.tar.gz belongs to -f (the file tar is going to read) and /tmp/restore belongs to -C (the directory tar should move to before extracting).

Why it works: it is the same pattern as ls -la, with the particularity that a flag that needs a value (-f) must go last inside the combo, because the value that follows on the line belongs to that last letter.

2. With a single command, create a folder named exactly weekly report (with a space) inside your home folder. It should not create two folders.

See solution
mkdir "weekly report"

(Also valid: mkdir 'weekly report' or mkdir weekly\ report.)

Why it works: the quotes tell the shell to treat the space as part of a single argument, instead of as the separator between two. mkdir receives one single argument and creates one single folder.

3. You have a file in your current folder that, by accident, is named -1.txt (starts with a dash). Write the command to delete it with rm without the shell interpreting it as an option.

See solution
rm -- -1.txt

(Also valid: rm ./-1.txt, because prefixing ./ makes the name no longer literally start with a dash.)

Why it works: -- tells rm that everything that follows is a positional argument, not an option, even though it starts with a dash.

4. Deliberately run a command that does not exist (for example, misspell the name of a program you do have installed). Explain, using what you learned in this lesson, exactly what the error you get means and what it does NOT mean.

See solution

For example, if you type gitt status instead of git status, you will see something like:

zsh: command not found: gitt

It means the shell searched, in every folder listed in the PATH variable, for a program named exactly gitt, and did not find it in any of them.

It does not mean git (the real program) is not installed — in fact it probably is. It also does not mean something is wrong with the status argument: the error happened before the shell even got to considering the arguments, because it could not even identify which program to run.

Why it works: telling this apart from a "file not found" error (which would come from the program once it did start) is exactly what keeps you from wasting time checking the wrong place the next time you run into a similar error.

Summary and next step

Before moving on, you should be able to:

  • split any command into command, options, and arguments just by looking at it;
  • recognize when several short flags are combined and why one with a value must go last in the combo;
  • use quotes when an argument has spaces;
  • use -- when an argument starts with a dash;
  • explain precisely what command not found means (and does not mean);
  • move around the command line with arrows, Tab, Ctrl+C, Ctrl+L, Ctrl+A, and Ctrl+E without thinking about it.

You now know how to read the shape of a command. What you still do not know is which options a particular command you have never seen before accepts — and that is exactly where the next lesson comes in: reading the manual (man), --help, and tldr to become self-sufficient without depending on a tutorial every time you run into a new program.

Resources