Commands & Syntax

The Anatomy of a Command

Bash has a small grammar. Almost everything you do reduces to simple commands joined by operators. Once you can name the parts of a command, syntax errors stop looking random.

Simple Commands

A simple command is a sequence of words separated by blanks, terminated by a newline or a separator. The first word is the command; the rest are arguments. The shell expands the words, then asks the kernel to run the result.

ls -l /var/log          # command=ls   args=-l /var/log
echo "hi" "there"       # command=echo args=hi there (quotes removed after expansion)
git commit -m "fix"     # command=git  args=commit -m fix

Word splitting is the subtle part: an unquoted variable may become several words, and those words become several arguments. Quoting is covered in its own lesson, but remember the rule now — quote unless you can explain why not to.

Arguments & Options

Conventionally, options start with a dash. Short options can be bundled (-la equals -l -a) and often take a value either separated or attached.

ls -la            # -l long, -a all  (bundled)
grep -n "TODO" f  # -n adds line numbers
head -n 20 f      # separated value
head -20 f        # attached value (classic form)
--                # the double dash ends options; anything after is a filename
rm -- -weird.txt  # delete a file literally named "-weird.txt"

Exit Status

Every command returns a status: 0 = success, non-zero = failure. $? holds the status of the last command. This is the backbone of all scripting logic.

ls /etc >/dev/null     # discard output, keep the status
echo $?                # 0

ls /nope 2>/dev/null
echo $?                # 2 — a non-zero status means "handle the failure"

Where Commands Come From

Builtins vs External

Some words are handled by Bash itself; these are builtins. They must run in the shell process because they change the shell's own state — a child process could not change its parent.

cd /tmp        # builtin: changes the shell's working directory
pwd            # builtin: prints the shell's directory
export FOO=1   # builtin: puts FOO into the shell's environment
read -r line   # builtin: reads from the shell's standard input

Anything not a builtin is an external command: a file on disk that Bash forks and executes. /bin/ls, /usr/bin/grep and your own scripts are all external.

PATH Lookup

When you type a bare word, Bash searches each directory listed in PATH, left to right, and runs the first match. That is why ./script.sh is needed for the current directory — it is usually not on PATH.

echo "$PATH"                  # /usr/local/bin:/usr/bin:/bin ...
which python3                 # shows the first match on PATH
PATH="$HOME/bin:$PATH"        # prepend a personal bin directory

type & command

type tells you how Bash would resolve a name; command bypasses functions and aliases to reach the real builtin or binary.

type cd          # cd is a shell builtin
type ls          # ls is /usr/bin/ls
type -a echo     # every definition, in lookup order
command ls       # run the real ls even if a function named ls exists

Command Separators

Semicolon & Newline

A newline ends a command. A semicolon does the same thing on one line. A backslash-newline joins lines.

echo one; echo two          # two commands, one line
echo three + \
     four                   # line continuation: one command "echo three + four"

&& and ||

&& runs the right side only if the left succeeded; || runs it only if the left failed. They are the idiomatic one-line if.

mkdir data && cd data                 # cd only if mkdir worked
ping -c1 host >/dev/null || echo "down"
test -f cfg || { echo "missing cfg"; exit 1; }

Grouping with ( ) and { }

Parentheses run commands in a subshell (a copy of the shell); braces group them in the current shell. The difference decides whether state survives.

( cd /tmp && pwd )     # subshell: the real shell stays put
{ cd /tmp; pwd; }       # current shell: the directory change persists
A brace group needs a space after { and a semicolon (or newline) before }: { echo hi; }. Parentheses do not.