Skip to content
DevOps devops shell 4 min read

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 break if tests.

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)
ReadabilityEasy to confuse with a quote 'Clear, obvious boundaries
NestingPainful — needs ``` escapingJust nest naturally: $(a $(b))
Recommended in 2026No (legacy)Yes — always
POSIX supportYesYes

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 )) gives 3, not 3.5. For decimals use a tool like bc (an arbitrary-precision calculator: echo "scale=2; 7/2" | bc) or awk.

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 bc or awk when you need decimals.
  • Inside $(( )) you can skip the $ before variable names, but stay consistent across your script.
  • Prefer $(( )) for producing values and (( )) / let for in-place counter updates.
  • Check that a captured command actually succeeded — a failed command may return an empty string and silently break later logic.
Last updated June 15, 2026
Was this helpful?