Skip to content
VLSI Mentor

Linux & Bash · Part I · Module 8 · Variables, Command Substitution and Arithmetic

Creating and Using a Shell Variable

A variable is a name you give to a piece of text so you can use it again. The idea takes thirty seconds; the two rules that come with it take rather longer, and skipping either produces an error message that does not obviously point back at what you typed.

Foundation17 min readLinuxPart IFundamentalsVariablesBash

Part I · Module 8 · Chapter 8.1 · Creating and Using a Shell Variable

Everything so far has been commands acting on things that already exist. This module is about making the shell hold something — a value you computed once and want to use three more times — which is the step between using a shell and programming one.

1. Storing and Reading

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ name=run_42

$ echo "$name"
run_42

Two halves, and they use different syntax, which is the first thing to fix in your head:

  • To store, write the name, an equals sign, and the value: name=run_42. No dollar sign.
  • To read, write a dollar sign in front of the name: $name. Always a dollar sign.

The asymmetry is not arbitrary. On the left of an = the shell wants a name; everywhere else, a bare word is just a word, and $ is what tells the shell to replace it with a value.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ name=run_42

$ echo name
name

$ echo $name
run_42

The first echo printed the word name, because that is what you typed. Only the dollar sign makes it a variable reference.

2. The Spaces Rule

This is the single most common error in the whole of Bash, and it is worth meeting all three versions of it, because they produce three different messages.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ name=run_42
$ echo "$name"
run_42

That works. Now add a space.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ name = run_42
bash: name: command not found
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ name =run_42
bash: name: command not found
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ name= run_42
bash: run_42: command not found

No spaces are allowed around the = in an assignment. The reason is that the shell decides what a line is by looking at the first word. With a space, name is no longer part of an assignment — it is the first word of a command line, so the shell goes looking for a program called name, and there isn't one.

3. Everything Is Text

The second rule surprises people coming from other languages.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ n=5

$ echo "$n+1"
5+1

$ echo $n+1
5+1

5+1, not 6. A shell variable holds a string. n=5 stores the one-character text 5, and $n+1 produces the three-character text 5+1, because nothing in that line asked for arithmetic.

There is no "number" type to declare and nothing is being coerced. Arithmetic requires asking for it explicitly, which is Chapter 8.5:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ echo "$(( n + 1 ))"
6

This is not a flaw. The shell's job is to assemble command lines out of text, so text is the only type it needs — and a value that looks like a number is only ever a number because some command chose to read it that way.

4. Expansion Happens Before the Command Runs

Here is the model that makes the rest of this module — and the whole of Module 9 — make sense.

When you press Enter, the shell does not hand your line to the command. It processes the line first: it replaces every $name with that variable's value, decides how the result breaks into separate arguments, and only then starts the program.

So the command never sees $name. By the time it exists, there is no variable left — only the text that replaced it.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ f="my report.txt"

$ ls $f
ls: my: No such file or directory
ls: report.txt: No such file or directory

Read the two errors carefully. ls did not complain about a file called my report.txt. It complained about two files, my and report.txt, because that is genuinely what it was given. The shell replaced $f with my report.txt and then split that text at the space into two separate arguments.

Quotes prevent the splitting:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ ls -l "$f"
-rw-r--r-- 1 engineer engineer 0 Mar 28 12:49 my report.txt
The shell replaces a variable with its value before the command runs, and then decides how many arguments that value becomes. Without quotes the value is split at every space, so a filename containing a space arrives as two separate arguments. With quotes it arrives as one.What you typels $f — no quotesWhat the shell buildsthe value is split at everyspaceWhat the command getstwo arguments — and two errorsWhat you typels "$f" — quotedWhat the shell buildsthe value is kept wholeWhat the command getsone argument — the file youmeantexpandssplitsexpands12
Figure 1 — what the shell does with a variable before the command ever starts. In both rows the variable is replaced by its value; the difference is what happens next. Unquoted, the resulting text is split at every space, so a file name containing a space arrives as two separate arguments and the command reports two errors. Quoted, the value is kept whole and arrives as the single argument you intended.

5. Naming

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ ok_1=x
$ echo "$ok_1"
x

$ 2bad=x
bash: 2bad=x: command not found

$ my-var=x
bash: my-var=x: command not found

A variable name may contain letters, digits and underscores, and may not start with a digit. Nothing else — no hyphens, no dots, no spaces. A name the shell does not recognise as a name means the line is not an assignment, which is why both failures report command not found rather than anything about naming.

Names are case-sensitive, and name and NAME are two different variables:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ name=lower
$ NAME=UPPER
$ echo "[$name] [$NAME]"
[lower] [UPPER]

6. Unset, Empty, and the Difference

A variable that was never set and a variable set to nothing both expand to nothing:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ empty=""
$ echo "empty=[$empty] missing=[$missing]"
empty=[] missing=[]

Indistinguishable from the outside — which is usually fine, and occasionally exactly the bug. Chapter 8.2 gives you a way to tell them apart.

unset removes a variable entirely:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ v=1
$ unset v
$ echo "after unset: [$v]"
after unset: []

Catching the typo

The default behaviour — an unset variable silently expands to nothing — is the single most common cause of a script that destroys the wrong thing. rm -rf "$TMPDIR/"* with TMPDIR misspelled becomes rm -rf /*.

Bash can be told to refuse instead:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ set -u

$ echo "[$missing]"
bash: missing: unbound variable

set -u makes expanding an unset variable an error. For anything that will be run unattended it is close to mandatory; it turns a silent catastrophe into a loud, early, specific failure.

(It is usually written as part of set -euo pipefail — Chapter 10.6 explains the set command itself, and its other two parts belong to Module 11 and Module 12. Chapter 20.2 covers what each one genuinely does and — importantly — what it does not.)

Fixing a value in place

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ readonly R=fixed
$ R=changed
bash: R: readonly variable

readonly prevents further assignment. It is worth using for the two or three values at the top of a script that define where everything else lives, precisely because those are the ones whose corruption does the most damage.

7. Variables Belong to One Shell

Everything in Chapter 7.4 about inheritance applies directly, and is worth restating in its new vocabulary:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ LOCAL=hello
$ bash -c 'echo "child sees: [$LOCAL]"'
child sees: []

$ export SHARED=hello
$ bash -c 'echo "child sees: [$SHARED]"'
child sees: [hello]

A plain assignment makes a shell variable, which lives in one shell process. export additionally places it in the environment, which is what children receive a copy of. Changes a child makes never return.

That is also why a variable set in a script you run is gone afterwards, and a variable set in a script you source is not — the same one-way rule, seen from the variable's side.

8. Using Variables for Real

The point of all this is to stop repeating yourself. Here is a sequence most engineers type in some form every week:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
project=/home/engineer/project
run=run_42
logdir=$project/sim/logs

mkdir -p "$logdir"
cp "$project/rtl/alu.sv" "$project/sim/"
echo "starting $run"

Four things that improved:

  • The project path is written once. Moving the work means editing one line.
  • The run name is written once, so there is no chance of run_42 in one place and run_43 in another.
  • logdir is built from project, so it cannot drift out of step with it.
  • Every expansion that reaches a command is quoted. The paths here contain no spaces — but the next project's might, and the script should not care.

Note logdir=$project/sim/logs: a variable's value can be built from other variables, and the right-hand side of an assignment is one of the few places where omitting the quotes is safe, because no word splitting happens there. Quoting it anyway costs nothing and keeps the habit intact.

Practice

1 — Meet all three spacing errors. Type each of these and read the message before fixing it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
name = run_42
name =run_42
name= run_42
name=run_42

Then explain to yourself why the third one names a different word.

2 — Prove the value is text.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
n=5
echo "$n+1"
echo "$(( n + 1 ))"

3 — Make the splitting failure happen, then fix it.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
f="my report.txt"
touch "$f"
ls -l $f          # two errors
ls -l "$f"        # one file
rm "$f"

4 — Build a path from parts.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
project=/home/engineer/project
sub=sim/logs
echo "$project/$sub"

5 — Tell unset from empty, before reading 8.2. Set one variable to the empty string and leave another unset. Confirm echo cannot distinguish them. Then turn on set -u in a subshell so you do not have to live with it, and watch only one of them fail:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
e=""
( set -u; echo "[$e]"; echo "[$nope]" )

6 — Protect a value.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
readonly PROJECT=/home/engineer/project
PROJECT=/tmp
echo "$PROJECT"

7 — Confirm the export boundary. Set one variable plainly and one with export, then run bash -c 'echo ...' for each.

8 — Check the convention. Run env | head -20. Count how many are uppercase.

Check Your Understanding

1. count = 5 reports count: command not found. Why does the message name count rather than complaining about the equals sign?

2. n=10 and then echo $n/2 prints 10/2. Is this a bug?

3. You have f="Q3 report.txt" and run wc -l $f. What does wc actually receive, and what will it report?

4. Why can you write logdir=$project/sim without quotes, but not mkdir $logdir?

5. A script does rm -rf "$BUILDDIR"/* and BUILDDIR is misspelled. What happens, and what one line would have prevented it?

6. MARKER=x in a script you run with ./setup.sh does not exist afterwards. export MARKER=x does not either. Why not, and what does work?

9. Remember

  • Assign without a dollar sign, read with one: name=run_42, then "$name".
  • No spaces around the =. With a space the line stops being an assignment and becomes a command.
  • name= value is valid syntax doing something else — running value with name set for that command only. That is why its error names the wrong word.
  • Every value is text. n=5 stores a character, not a number; arithmetic must be requested with $(( )).
  • Expansion happens before the command runs. The command never sees $name, only what replaced it.
  • Quote every expansion: "$name". The unquoted form works until a value contains a space, and then fails by inventing arguments.
  • Names are letters, digits and underscores, not starting with a digit, and are case-sensitive.
  • UPPERCASE for environment variables, lowercase for your own — a convention worth following so you never collide with PATH.
  • Unset and empty both expand to nothing. Chapter 8.2 tells them apart.
  • set -u turns an unset variable into an error, which is the difference between a script that stops and a script that deletes the wrong directory.
  • readonly fixes a value; unset removes one.
  • A plain assignment is local to one shell; export puts it in the environment that children inherit — and nothing a child does comes back.

That is one variable holding one piece of text. The next chapter is about the forms that go around the name — how to say "use this, or this default if it is not set", how to get a value's length, and how to attach an expansion directly to other text without the shell reading it as one long name.

Where this fits

Part of the Linux & Bash curriculum.