Variables & Macro Expansion
1 Variable Assignment
In ksh, a variable stores a value that can be reused later in commands or scripts. Assigning a value to a variable is straightforward: write the variable name, immediately followed by an equals sign and the value — with no spaces around the = sign.
Syntax
| name=value |
Some practical examples:
| greeting=Hello count=42 filename=report.txt dir=/home/user/documents |
| Important: There must be no spaces on either side of the = sign. Writing name = value is a syntax error because ksh will interpret name as a command name. |
Variable names may contain letters, digits, and underscores, and must begin with a letter or underscore. By convention, environment variables (those used by the system) are in UPPER_CASE, and variables used in scripts use lower_case, though ksh does not enforce this.
2 Macro Expansion
Macro expansion (also called parameter expansion or variable substitution) is the process by which ksh replaces a variable reference or expression with its current value before executing a command. The echo command is ideal for exploring expansion because it simply prints whatever arguments it receives. You may be familiar with echo from MS-DOS and Windows, where variables are bracketed with a % character. But Unix shell variables are a more mature system.
2.1 The $ Prefix for Variable Names
Prefix a variable name with $ to expand it. The shell sees a $ and knows that what follows is something it needs to substitute. It’s not just used for variables, but this is a common use case.
Basically the shell goes through a line it’s about to run and “fixes up” anything prefixed with a $, and certain other characters, before it runs it.
| Command city=Swansea echo $city | Output Swansea |
You can embed variable expansions anywhere in a string or command line:
| Command user=Alice echo “Hello, $user!” | Output Hello, Alice! |
If your variable expansion runs on into more alphanumeric characters you will need to wrap it in a { and } to avoid ambiguity. Otherwise the { and } are optional.
| Command ext=jpg echo “photo.${ext}” dir=/var/tmp echo $dir123 echo ${dir}123 | Output photo.jpg Problem! No output /var/tmp123 |
| Tip: Using ${varname} to avoid ambiguity, but omitting it can sometimes improve readability |
2.2 Glob Expansion
Glob expansion (also called pathname expansion or filename generation) lets you use wildcard characters in a command to match multiple file or directory names. ksh performs the expansion before passing the resulting list to the command.
Common wildcard characters
| * matches any sequence of characters (including none) ? matches exactly one character [abc] matches any one of the listed characters [a-z] matches any character in the given range |
Examples using echo to reveal what the shell expands:
| Command echo /etc/*.conf | Output /etc/host.conf /etc/nsswitch.conf /etc/resolv.conf … |
| Command echo file?.txt | Output file1.txt file2.txt file9.txt |
| Command echo report_[0-9][0-9].csv | Output report_01.csv report_12.csv report_99.csv |
| Note: If no files match a glob pattern, ksh leaves the pattern unchanged and passes it literally to the command. This is a common source of confusion when a path does not yet exist. |
2.3 Command Substitution: $() Syntax
Command substitution lets you capture the output of a command and use it as a value — either storing it in a variable or embedding it directly in another command.
| result=$(command) |
The shell runs command in a subshell, collects its standard output (with trailing newlines stripped), and substitutes the result in place of the $(…) expression. Note that the date command prints the current time and date, and the format of it’s output can be modified using a template prefixed with +. We’ll cover this later. The wc -l command simply counts the number of lines in the input.
| Command today=$(date +%Y-%m-%d) echo $today | Output 2024-03-15 |
| Command echo “You have $(ls | wc -l) files here.” | Output You have 23 files here. |
Backtick alternative
An older syntax uses backtick characters ( ` ) instead of $(…). This is the character on the keyboard found on most keyboards to the left of the digit 1.It’s equivalent in basic use:
| result=`command` # These two lines produce identical results: today=$(date +%Y-%m-%d) today=`date +%Y-%m-%d` |
The $() form is strongly preferred for several reasons:
- It can be nested cleanly: $(outer $(inner))
- Backticks require backslash-escaping inside nested calls, which quickly becomes unreadable.
- $() syntax is visually clear and consistent with other shell constructs.
- It’s easy to miss a back-tick or confuse it with an apostrophe.
| Tip: Use $() in all new scripts. Backticks are found in older scripts and are worth recognising, but should not be written in new code, but are less typing if you’re running a one-off command. |
3 Quoting to Prevent Expansion
Sometimes you want ksh to treat special characters — $ * ? and others — literally rather than expanding them. Quoting provides three ways to do this, each with different scope and purpose.
3.1 Backslash ( \ )
A backslash immediately before a character escapes that single character — the shell treats the following character literally instead of interpreting it.
| Command price=5 echo The cost is \$price | Output The cost is $price |
| Command echo show \* without glob | Output show * without glob |
Backslash escape is precise: it affects only the one character that immediately follows it.
3.2 Single Quotes ( ‘ )
Enclosing text in single quotes prevents all expansion within those quotes. Every character is taken literally — the shell performs no substitution, no glob expansion, and no command substitution.
| Command name=World echo ‘Hello $name’ | Output Hello $name |
| Command echo ‘*.txt files are $important’ | Output *.txt files are $important |
| Note: You cannot include a single-quote character inside a single-quoted string. To use a literal single quote, end the string, escape the quote with a backslash, and reopen the string: echo ‘it’\”s here’ |
3.3 Double Quotes ( ” )
Double quotes suppress glob expansion and word splitting, but still allow variable expansion ($var and ${var}) and command substitution ($(…)). They are the most commonly used form of quoting in scripts.
| Command name=Alice echo “Hello, $name” | Output Hello, Alice |
| Command echo “Today is $(date +%A)” | Output Today is Wednesday |
| Command echo “No glob: *.txt” | Output No glob: *.txt |
Quick comparison
| Quoting | $var expansion | $() substitution | Glob expansion |
| \backslash | Yes (next char only) | Yes (next char only) | Yes (next char only) |
| ‘single quotes’ | No | No | No |
| “double quotes” | Yes | Yes | No |
| (no quoting) | Yes | Yes | Yes |
4 Using a Variable in a Command
A practical and very common use of variable expansion is building up path names or directory fragments that are then passed to commands such as ls.
Partial directory name in ls
Suppose you are frequently working inside a versioned project directory and do not want to retype the path each time. Store part of the path in a variable and let the shell expand it for you:
| # Store a partial path project=/home/alice/projects/webapp # Use it with ls to list a subdirectory ls ${project}/src # Combine with a glob to find only Python files ls ${project}/src/*.py # Use in a longer path with another variable subdir=tests ls ${project}/${subdir} |
Notice that braces are used around the variable name (i.e. ${project}) to avoid unexpected problems if there was something funny in $project.
You can also store just a partial directory segment — a prefix — and rely on glob expansion to complete it:
| # List all log directories for a given year year=2024 ls /var/log/app/${year}-* # List any directory whose name starts with ‘web’ prefix=web ls /srv/${prefix}*/html |
| Command prefix=web ls /srv/${prefix}*/html | Output /srv/webapp/html /srv/webapi/html /srv/webstatic/html |
| Tip: Always quote the expanded variable in double quotes if the path might contain spaces: ls “${project}/My Documents” |
5 Exercises
Work through the following exercises at a ksh prompt. Type the commands exactly as shown, observe the output, then experiment with variations to deepen your understanding.
Section A — Variable Assignment and $ Expansion
| Exercise 1: Assign your first name to a variable called firstname and your surname to lastname. Use echo to print both on a single line in the form Firstname Lastname. Hint: Two separate echo arguments, or embed both variables in one double-quoted string. |
| Exercise 2: Create a variable ext with the value txt. Then use echo with brace syntax (${}) to print the string myfile.txt without writing .txt literally in the echo command. Hint: echo “myfile.${ext}” |
| Exercise 3: Assign any number to a variable n. Use echo to print the line: The value of n is: 42 (substituting your number). Then change n to a different number and run the same echo command again without retyping it — use the Up arrow to recall it. Hint: Re-run with !! or the Up arrow after changing n. |
Section B — Glob Expansion
| Exercise 4: Run echo /etc/p* and observe which files and directories are matched. Then try echo /etc/p*.conf — how does the output differ? Hint: The * wildcard matches any sequence of characters. |
| Exercise 5: In your home directory, create three files named note1.txt, note2.txt, and notex.txt using touch. Then use echo note?.txt and explain what the ? wildcard matched compared to echo note*.txt. Hint: touch note1.txt note2.txt notex.txt — then compare the two echo outputs. |
| Exercise 6: Use echo with a character-class glob to list only files in /etc whose names begin with a vowel (a, e, i, o, or u). Hint: echo /etc/[aeiou]* |
Section C — Command Substitution
| Exercise 7: Capture the current working directory into a variable called here using $() and the pwd command. Then echo the sentence: You are in: /your/path Hint: here=$(pwd) |
| Exercise 8: Store the number of lines in /etc/passwd into a variable usercount using $() and wc -l. Print a sentence such as: There are 42 users in /etc/passwd. Hint: usercount=$(wc -l < /etc/passwd) |
| Exercise 9: Rewrite your answer to exercise 7 using backtick syntax instead of $(). Confirm the output is identical, then consider: which form do you find easier to read? Hint: here=`pwd` |
Section D — Quoting
| Exercise 10: Assign the string don’t stop to a variable msg. Then echo $msg and verify the apostrophe is preserved. (Hint: use double quotes for the assignment: msg=”don’t stop”) Hint: Double quotes allow the apostrophe inside the string. |
| Exercise 11: Run these three commands and explain why each produces different output: name=World echo Hello $name echo ‘Hello $name’ echo “Hello $name” Hint: Unquoted and double-quoted expand $name; single-quoted does not. |
| Exercise 12: Use a backslash to print the literal string Cost: $10.00 without ksh expanding $10 as a variable. Try both the backslash approach and the single-quote approach and confirm they give the same result. Hint: echo Cost: \$10.00 vs echo ‘Cost: $10.00’ |
Section E — Variables in Commands
| Exercise 13: Set a variable logdir to /var/log. Use it in an ls command to list all files in that directory ending in .log. Use brace syntax for the variable. Hint: ls ${logdir}/*.log |
| Exercise 14: Store a partial directory name — such as sys — in a variable called fragment. Then use ls with a glob combining ${fragment} and * to list any matching entries under /var/log. Hint: fragment=sys; ls /var/log/${fragment}* |
| Exercise 15: Create variables for the first part of a filename and its extension. Use echo (not ls) to demonstrate that combining them with ${} produces the expected filename string — for example report_2024.pdf. Hint: base=report_2024; ext=pdf; echo “${base}.${ext}” |

