skip to content

In a bash script, the line `count = 5` fails with `count: command not found`. Why does bash reject spaces around `=` in an assignment, and what are the correct forms when the value itself contains spaces?

level: juniorimportance: must knowfreq 72%

answer

  1. the shell sees words, then a command
  2. = has to touch both sides
  3. spaces turn it into a command call
  4. the right side is a single word
  5. a space before 5 makes 5 the command

basics

~20 s

Bash parses count = 5 as the command count with the arguments = and 5. An assignment must have no spaces around =: write count=5, and quote the value when it contains spaces, as in msg="hello world".

solid answer

~40 s

Bash is a command language, not an expression language. Before anything else, it splits the line into words on whitespace and treats the first word as a command name. An assignment is recognised only when the very first word matches the shape `NAME=value` with no space on either side of the `=`, so `count = 5` is parsed as running the command `count` with the arguments `=` and `5` — hence `command not found`. The correct form is `count=5`. Because the right-hand side is still a single word, a value containing spaces must be quoted: `msg="hello world"`. Two near misses behave differently and are worth knowing: `count =5` still runs the command `count`, while `count= 5` sets `count` to the empty string just for the command `5`, which then fails to run.

code

bash · 15 lines
bash
#!/usr/bin/env bash
count=5
echo "count is $count"

msg="hello world"
echo "msg is [$msg]"

# these three do NOT assign what you expect:
# count = 5    -> runs the command 'count' with args '=' and '5'
# count =5     -> runs the command 'count' with arg '=5'
# count= 5     -> sets count empty for one command, then runs '5'

empty=
unset gone
echo "empty is set to [${empty}]"

go deeper

for a junior

Be able to say instantly that bash assignments take no spaces around =, and that a value containing spaces must be quoted. Write count=5 and msg="hello world" without hesitating.

for a middle

Explain the parsing rule behind the error: bash splits the line into words and only a leading NAME=value word counts as an assignment, so count = 5 becomes a command invocation. Know that count= 5 is a legal prefix assignment, not a syntax error.

for a senior

Talk about the failure mode rather than the syntax — a missed assignment leaves the variable unset, and an empty expansion later can produce a destructive command. Point at strict mode and linting as the systematic defence instead of code review vigilance.

for a principal

Frame it as why shell needs mandatory tooling: the language has no compile step and its parser silently reinterprets a typo as a valid command. Argue for a linted, reviewed shell baseline, and for a rule about when a script has outgrown bash entirely.

## Bash parses words, not statements Most languages have a grammar in which `=` is an operator and surrounding whitespace is irrelevant. Bash does not. A bash line is first split into *words* on unquoted whitespace, and the shell then decides what kind of command it is looking at based on the shape of those words. The rule for a variable assignment is narrow: the word must match `NAME=word`, where `NAME` is a valid identifier (letters, digits and underscores, not starting with a digit) and the `=` is part of the same word. Write `count = 5` and you have produced three words: `count`, `=`, and `5`. None of them is an assignment, so bash falls back to the ordinary rule — the first word is a command name and the rest are its arguments. There is no program called `count` on a typical system, so you get `count: command not found` with exit status 127. ## The correct forms ```bash count=5 # integer-looking value, one word, fine unquoted msg="hello world" # value has a space, so it must be quoted path=/usr/local/bin # slashes are fine, no quoting needed empty= # sets empty to the empty string (it IS set) ``` The right-hand side of an assignment is a single word after quote removal. `msg=hello world` does not assign two words to `msg`; it assigns `hello` to `msg` *for the duration of the command `world`*, and then tries to run `world`. Quoting is what keeps the value together. One thing that surprises people coming from other shells or languages: you never put a `$` on the left. `$count=5` expands `count` first and then tries to run the resulting text as a command. The `$` is for *reading* a variable; a bare name is for *writing* it. ## The three near misses, precisely | Line | What bash actually does | |---|---| | `count=5` | Assigns `5` to `count` in the current shell | | `count = 5` | Runs the command `count` with arguments `=` and `5` | | `count =5` | Runs the command `count` with the argument `=5` | | `count= 5` | Sets `count` to empty for one command, then tries to run `5` | That last row is the interesting one, because it is not an error in the parser at all — it is the legal *prefix assignment* form, where one or more `NAME=value` words in front of a command apply to that command only. `count= 5` is a perfectly well-formed command line; it just names a command, `5`, that does not exist. ## Why this trips people up in reviews The failure mode is quiet. Because the shell sees a command rather than an assignment, the script keeps going with the variable unset, and the symptom shows up much later as an empty expansion — a `rm -rf "$prefix/data"` that becomes `rm -rf /data`, or a curl to `https:///api`. If the script runs with `set -u`, the later read of the unset variable at least aborts; without it, an empty value silently propagates. This is exactly the class of bug linters catch cheaply, and ShellCheck flags the pattern rather than waiting for runtime. ## Assignment versus other things that look like it The same no-spaces rule applies anywhere bash expects a `NAME=value` word, and the error messages differ: ```bash export PATH = /usr/bin # export: `=': not a valid identifier export PATH=/usr/bin # correct declare -i n = 5 # declare: `=': not a valid identifier declare -i n=5 # correct ``` `export`, `declare`, `readonly` and `local` are builtins that take `NAME` or `NAME=value` *arguments*. Splitting the argument with spaces hands them three separate arguments, and `=` on its own is not a legal identifier. ## Set versus unset versus empty Finally, `empty=` and `unset empty` are not the same thing. After `empty=`, the variable exists and holds the empty string; after `unset empty`, the name is gone. Under `set -u` the first is a perfectly valid read and the second aborts the script, and the parameter-expansion operators that distinguish the two cases exist precisely because the distinction is real.

  • What is the difference between `count =5` and `count= 5`?
    `count =5` is still a command invocation: bash runs `count` with the single argument `=5`. `count= 5` is a valid prefix assignment — it sets `count` to the empty string in the environment of the command that follows, and that command is `5`, which does not exist. The first fails on the name `count`, the second on the name `5`.
  • Why does `export PATH = /usr/bin` also fail?
    `export` takes arguments of the form `NAME` or `NAME=value`. Splitting on spaces hands it three arguments: `PATH`, `=` and `/usr/bin`. Bash exports `PATH` unchanged, then rejects `=` and `/usr/bin` because neither is a valid identifier. The fix is the same as for a plain assignment: `export PATH=/usr/bin`, with no spaces.
  • Is `x=` the same as `unset x`?
    No. `x=` leaves `x` defined and holding the empty string, so under `set -u` reading `$x` is fine. `unset x` removes the name entirely, and reading it under `set -u` aborts the script. Code that tests whether a value was supplied at all has to distinguish the two cases, not just check for emptiness.

saying these in an interview costs you the question

  • Says bash allows spaces around = like Python
  • Writes $count=5 with a dollar sign on the left
  • Assigns a multi-word value without quoting it
  • Thinks the error means the variable name is invalid
  • Believes x= and unset x mean the same thing

context