
Command Substitution and Arithmetic in Bash
In the world of shell scripting, you often need to capture the output of commands and perform arithmetic operations inside your script. Whether you’re automating system checks, processing files, or managing users, these two capabilities — command substitution and arithmetic expansion — form the logical and computational backbone of your scripts.
What is Command Substitution?
Command substitution allows you to execute a command and use its output as a value within another command or a variable assignment.
Syntax:
There are two syntaxes for command substitution in Bash:
$(command) # Recommended modern syntax
command
# Older backtick syntax (less readable)
Example:
today=$(date)
echo “Today’s date is: $today”
Here, the date command is executed, and its output is stored in the today variable.
Old Syntax vs. New Syntax
Bash scripting has evolved over time. Earlier practices (commonly seen in older shell scripts or inherited systems) were often more verbose or less consistent. As Bash matured, cleaner, more readable, and more efficient ways of doing the same tasks became common.
Let’s explore the differences between old syntax and modern (recommended) syntax in Bash scripting.
- Variable Declaration and Expansion
Old Style:
VAR=value
echo $VAR
Still functional, but can cause parsing errors in complex scripts or when embedded inside strings.
New Style:
VAR=“value”
echo “${VAR}”
Why Better?
Quoting “value” protects against word splitting.
${VAR} ensures clarity when variables are used with other strings (like echo “Hello, ${USER}123”).
- Command Substitution
Old Style:
today=date
New Style:
today=$(date)
Why Better?
$(command) is more readable, especially when nesting commands:
result=$(grep $(date +%Y) logfile.txt)
Backticks (`) are harder to nest and less visually clear.
- Arithmetic Operations
Old Style:
let “sum = a + b”
New Style:
sum=$((a + b))
Why Better?
$((expression)) is cleaner, more flexible, and behaves more like a modern programming language.
let is more error-prone and less preferred today.
- Test Expressions (Conditional Statements)
Old Style (Single square brackets):
if [ “$a” -eq “$b” ]; then
echo “Equal”
fi
New Style (Double square brackets):
if [[ $a -eq $b ]]; then
echo “Equal”
fi
Why Better?
[[ … ]] supports:
Regex matching
Logical operators (&&, ||)
No word splitting or pathname expansion
- String Comparison
Old Style:
if [ “$a” = “$b” ]; then
echo “Match”
fi
New Style:
if [[ $a == $b ]]; then
echo “Match”
fi
Why Better?
[[ … ]] lets you use ==, !=, and pattern matching more cleanly.
No need to quote variables when using [[ … ]].
- Function Declaration
Old Style:
function greet {
echo “Hello”
}
New Style:
greet() {
echo “Hello”
}
Why Better?
POSIX-compatible and simpler.
No need for the function keyword unless you’re writing Bash-specific features.
Case Statements
No major syntax change, but newer practices emphasize indentation and quote-wrapping for safety.
case “$option” in
start)
echo “Starting…”
;;
stop)
echo “Stopping…”
;;
*)
echo “Invalid”
;;
esac
Redirecting Output
Old Style:
command > file 2>&1
New Style:
command &> file
Why Better?
Cleaner when you want both stdout and stderr redirected to a file.
Supported in Bash 4+.
Nesting Command Substitution
One of the reasons $() is preferred is because it’s easier to nest command substitutions.
echo “The number of users currently logged in: $(who | wc -l)”
This runs the who command, pipes it to wc -l, and prints the result.
Common Use Cases for Command Substitution
Assigning output to a variable
kernel_version=$(uname -r)
Using output inside another command
mkdir “backup_$(date +%F)”
Dynamic filenames
cp file.txt “file_backup_$(date +%H%M%S).txt”
Arithmetic in Bash
Bash can perform integer arithmetic (not floating point) using arithmetic expansion.
Basic Syntax:
$(( expression ))
This evaluates a numeric expression and returns the result.
Example:
a=10
b=5
sum=$((a + b))
echo “Sum: $sum”
Note: Variables inside (( )) don’t need the $ sign, but using it doesn’t break the expression either.
Supported Arithmetic Operators
Operator Function Example
- - Addition - ((a + b))
- - Subtraction - ((a - b))
- - Multiplication - ((a *b))
/ - Division - ((a / b))
% - Modulo (remainder) - ((a % b))
** - Exponentiation - ((a ** b))
++/– - Increment/decrement - ((a++))
Examples of Arithmetic Expansion
Basic math
x=8
y=3
echo “Multiplication: $((x * y))”
Calculating percentages
marks=450
total=500
percentage=$((marks * 100 / total))
echo “You scored $percentage%”
Using arithmetic directly in conditions
if (( x > y )); then
echo “x is greater than y”
fi
Important Notes
Bash only supports integers by default — no floating point.
For floating point math, use bc or awk.
Floating Point Example Using bc:
result=$(echo “scale=2; 10 / 3” | bc)
echo “Result: $result”
Combining Command Substitution and Arithmetic
You can nest them together:
file_count=$(ls | wc -l)
half_count=$((file_count / 2))
echo “Half the files: $half_count”
Practical Script Example
#!/bin/bash
read -p “Enter your name: ” name
start_time=$(date +%s)
echo “Processing data for $name…”
sleep 2 # simulate work
end_time=$(date +%s)
duration=$((end_time - start_time))
echo “Script executed in $duration seconds.”
This script shows how to:
Capture timestamps using date +%s
Common Pitfalls
Bash is a powerful scripting language, but it’s also known for its subtle quirks and syntax traps. Many developers—especially beginners—make small mistakes that cause unexpected behavior, security issues, or runtime errors.
Let’s explore some of the most common pitfalls in Bash scripting and how to avoid them with best practices.
- Unquoted Variables
❌ Mistake:
rm $file
If $file is empty or contains spaces like “my file.txt”, it can cause unintended deletion or errors.
✅ Correct:
rm “$file”
Rule: Always quote your variables unless you have a specific reason not to.
- Using == Inside Single Brackets
❌ Mistake:
if [ “$a” == “$b” ]; then
Single brackets ([ … ]) only support = for string comparison in POSIX.
✅ Correct:
if [ “$a” = “$b” ]; then
Or use double brackets for more flexibility:
if [[ $a == $b ]]; then
- Forgetting the Shebang
❌ Mistake:
Omitting #!/bin/bash at the top of your script can lead to execution under a different shell (like sh), breaking Bash-specific features.
✅ Correct:
#!/bin/bash
- Not Checking for Command Success
❌ Mistake:
cp important.txt /backup/
echo “Backup complete”
If the cp command fails, the script will still say “Backup complete”.
✅ Correct:
cp important.txt /backup/ && echo “Backup complete”
Or better:
if cp important.txt /backup/; then
echo “Backup complete”
else
echo “Backup failed”
fi
- Using == or -eq Without Variables
❌ Mistake:
if [ $a -eq 10 ]; then
If $a is empty or not a number, you’ll get a syntax error.
✅ Correct:
if [[ “$a” =~ ^[0-9]+$ ]] && [ “$a” -eq 10 ]; then
Or validate it beforehand:
if [[ -n “$a” ]]; then
- Not Making Scripts Executable
❌ Mistake:
Trying to run a script directly without making it executable:
./myscript.sh
Permission denied
✅ Correct:
chmod +x myscript.sh
./myscript.sh
- Using = Instead of -eq in Arithmetic
❌ Mistake:
if [ $a = 10 ]; then
This performs a string comparison, not an arithmetic one.
✅ Correct:
if [ $a -eq 10 ]; then
- Missing Spaces Around Brackets
❌ Mistake:
if[$a -eq 5];then
Will result in a syntax error.
✅ Correct:
if [ “$a” -eq 5 ]; then
Rule: Brackets must be surrounded by spaces.
- Using Backticks Instead of $(…)
❌ Mistake:
output=ls -l
Hard to nest, harder to read.
✅ Correct:
output=$(ls -l)
- Assuming cd Always Works
❌ Mistake:
cd /some/dir
#do stuff
If the directory doesn’t exist, your script may run in the wrong path.
✅ Correct:
cd /some/dir || {
echo “Directory not found!”
exit 1
}
- Not Using set -e for Fail-Fast Scripts
Without set -e, the script will continue even after a failure.
✅ Use:
#!/bin/bash
set -e
This causes the script to exit immediately if any command fails—ideal for deployment or automation scripts.
- Using Uninitialized Variables
❌ Mistake:
echo “Welcome, $username”
If $username is not set, it prints “Welcome, ”.
✅ Correct:
username=${username:-Guest}
echo “Welcome, $username”
- Overwriting Files Unintentionally
❌ Mistake:
cp file.txt /important/location/
If a file with the same name exists, it will be overwritten.
✅ Safer:
cp -i file.txt /important/location/
How to Avoid These Pitfalls
Always quote your variables.
Use [[ … ]] for robust conditionals.
Prefer $(…) over backticks.
Use set -euo pipefail for safe scripting.
Validate user input and file paths.
Test your script in safe environments before deploying.