Bash Script Sunday #1: Bash Parameter Expansion - Save Time and Avoid Subshells
Bash Scripting Sunday #1: Bash Parameter Expansion - Save Time and Avoid Subshells⌗
In this entry, I’d like to show you how to use Bash Parameter Expansion to make your life a little easier, your scripts a little quicker, and use less resources.
Here’s an example script that isn’t using Bash Parameter Expansion:
#!/usr/bin/env bash
filename="/path/to/file.txt"
echo "Basename: $(basename $filename)" # file.txt
echo "Dirname: $(dirname $filename)" # /path/to
This script will output:
Basename: file.txt
Dirname: /path/to
A simple script, but it’s forking two subprocesses; one for basename
and one for dirname
. Bash has this functionality built in with its Parameter Expansion.
Here’s an updated script to do the same that is using Bash Parameter Expansion:
#!/usr/bin/env bash
echo "Basename: ${filename##*/}"
echo "Dirname: ${filename%/*}"
The above updated script will not fork any subprocesses, and instead purely manipulate the already stored string variable $filename
In Bash, when you enclose the variable name inside {}
you are telling Bash that you wish to perform operations on the variable’s value, not just display it.
Removing Prefixes (#
and ##
)⌗
These operators remove the shortest or longest match from the beginning of a variable.
#
(Shortest Match)⌗
- Removes the shortest match of a pattern from the beginning.
filename="/path/to/file.txt"
echo "${filename#*/}" # "path/to/file.txt" (removes first "/")
##
(Longest Match)⌗
- Removes the longest match of a pattern from the beginning.
filename="/path/to/file.txt"
echo "${filename##*/}" # "file.txt" (removes everything before the last "/")
Removing Suffixes (%
and %%
)⌗
These operators remove the shortest or longest match from the end of a variable.
%
(Shortest Match)⌗
- Removes the shortest match of a pattern from the end.
filename="/path/to/file.txt"
echo "${filename%.*}" # "/path/to/file" (removes ".txt")
%%
(Longest Match)⌗
- Removes the longest match of a pattern from the end.
filename="/path/to/archive.tar.gz"
echo "${filename%%.*}" # "/path/to/archive" (removes everything after first ".")
Example Use Case⌗
Extracting a directory name or removing file extensions.
Benefits to using Bash Parameter Expansion⌗
Performance⌗
- Faster execution: Parameter expansion is done entirely within Bash, whereas
dirname
andbasename
require forking a new process. - When processing many files in a loop, avoiding subshells can significantly improve performance.
Less Resource Usage⌗
-
Calling
dirname
orbasename
spawns a new process every time, which consumes CPU and memory, especially in large loops. -
Example of inefficient code:
while read -r file; do dir=$(dirname $file") # Forks a new process base=$(basename "$file") # Another fork echo "Dir: $dir, Base: $base" done < filelist.txt
A better version using parameter expansion:
while read -r file; do echo "Dir: ${file%/*}, Base: ${file##*/}" done < filelist.txt
This avoids multiple subprocesses, making the script more efficient.
Portability & Consistency⌗
dirname
andbasename
may behave differently across Unix-like systems, especially with unusual file paths (like/
or empty strings).- Bash’s parameter expansion is consistent across different environments, ensuring predictable behaviour.
Less Dependency on External Tools⌗
- If you’re writing a script for minimal environments (like embedded Linux or containers with busybox),
basename
anddirname
might not be available. - Bash’s built-in expansion works as long as Bash is available.