Command Substitution & Arithmetic
Bash scripts become powerful the moment they stop hard-coding values and start computing them. Two features make that possible: command substitution (running a command and using its output as a value) and arithmetic expansion (doing math directly inside the shell). Without them you would have to type today’s date or the number of files by hand; with them your script figures it out at runtime. This page shows both, on Ubuntu 22.04/24.04 LTS, with commands that actually run.
Command substitution: capturing output into a variable
Command substitution means “run this command, throw away nothing, and hand me back what it printed so I can store it.” The modern syntax is $(command) — a dollar sign followed by the command in parentheses.
Here is the simplest useful example: save today’s date into a variable.
#!/usr/bin/env bash
today=$(date +%Y-%m-%d)
echo "Backup created on $today"
Output:
Backup created on 2026-06-15
The shell runs date +%Y-%m-%d first, captures the text it prints (2026-06-15), and assigns that text to the variable today. Note there are no spaces around the = sign in a Bash assignment — today = $(...) would fail.
Counting things
A very common DevOps task is counting files, lines, or processes. You pipe a command’s output into wc -l (wc is “word count”; -l counts lines) and capture the result.
#!/usr/bin/env bash
log_dir="/var/log/nginx"
count=$(ls "$log_dir" | wc -l)
echo "There are $count files in $log_dir"
Output:
There are 6 files in /var/log/nginx
Gotcha: Always wrap the substitution in double quotes when the result might contain spaces or be empty — for example
files="$(ls)". Without quotes, the shell splits the value on spaces and may mangle filenames or breakiftests.
Nesting and using output directly
You can nest $() inside another $(), which is one of the big reasons it beats the old style. You can also drop a substitution straight into another command without a variable at all.
#!/usr/bin/env bash
# Make a timestamped backup folder in one line
mkdir -p "/backups/$(date +%Y%m%d-%H%M%S)"
echo "Created: $(ls -dt /backups/* | head -1)"
Output:
Created: /backups/20260615-142233
Backticks are legacy — prefer $()
Older scripts use backticks (`command`) for the same job. They still work, but you should use $() in all new code.
| Feature | `command` (backticks) | $(command) (modern) |
|---|---|---|
| Readability | Easy to confuse with a quote ' | Clear, obvious boundaries |
| Nesting | Painful — needs ``` escaping | Just nest naturally: $(a $(b)) |
| Recommended in 2026 | No (legacy) | Yes — always |
| POSIX support | Yes | Yes |
When to use which: use $() every time. Only reach for backticks if you are forced to edit ancient scripts and want to stay consistent with the surrounding style — and even then, consider modernizing.
Arithmetic with $(( ))
Bash variables are normally just text. To do real math you use arithmetic expansion: $(( expression )). The double parentheses tell Bash “treat what is inside as numbers.” Inside them you do not need a $ before variable names (though it is allowed).
#!/usr/bin/env bash
disk_used=42
disk_total=100
percent=$(( disk_used * 100 / disk_total ))
echo "Disk is ${percent}% used"
Output:
Disk is 42% used
Supported operators include + - * / (add, subtract, multiply, divide), % (remainder/modulo), and ** (power). Comparisons like <, >, and == return 1 for true and 0 for false.
#!/usr/bin/env bash
total=$(( 2 ** 10 )) # power
remainder=$(( 17 % 5 )) # modulo
echo "1024? $total remainder? $remainder"
Output:
1024? 1024 remainder? 2
Gotcha: Bash arithmetic is integer only.
$(( 7 / 2 ))gives3, not3.5. For decimals use a tool likebc(an arbitrary-precision calculator:echo "scale=2; 7/2" | bc) orawk.
A practical counter
Combine substitution and arithmetic to build a running total — common in loops that tally results.
#!/usr/bin/env bash
errors=$(grep -c "error" /var/log/syslog)
warnings=$(grep -c "warning" /var/log/syslog)
issues=$(( errors + warnings ))
echo "Found $issues issues ($errors errors, $warnings warnings)"
Output:
Found 37 issues (12 errors, 25 warnings)
(grep -c counts matching lines, so each substitution returns a number ready for math.)
The let command
let is an older way to evaluate arithmetic. It performs the math but does not print or return a value the way $(( )) does — you assign inside it.
#!/usr/bin/env bash
count=5
let count=count+1
let "count += 10" # quotes needed when using spaces
echo "Count is $count"
Output:
Count is 16
When to use let vs $(( )): prefer $(( )) for computing a value you want to store or print, because it reads more clearly and is the standard idiom. Use let (or its cousin (( )) with no $) mainly for in-place updates like incrementing a loop counter: (( count++ )).
Best Practices
- Always use
$(command), never backticks, in new scripts — it nests cleanly and reads better. - Quote your substitutions (
var="$(...)") to protect against spaces, newlines, and empty results. - Remember Bash math is integer-only; switch to
bcorawkwhen you need decimals. - Inside
$(( ))you can skip the$before variable names, but stay consistent across your script. - Prefer
$(( ))for producing values and(( ))/letfor in-place counter updates. - Check that a captured command actually succeeded — a failed command may return an empty string and silently break later logic.